0

I've written a script so I can access my newly recorded media from a podcast player. The script runs as expected from the command line but when it runs from the task scheduler it is not getting the full path from subfolders. There are multiple errors on every foreach pass with this erroneous path. here is one such error

get-item : Cannot find path 'D:\Radio\202303100400.mp4' because it does not exist.
At C:\Users\jj9wo\mpxtorss.ps1:38 char:23
+ foreach ($folder in ((get-item $media).DirectoryName) | Select-Object ...
+                       ~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (D:\Radio\202303100400.mp4:String) [Get-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand

the correct path to the file is D:\Radio\kmox\202303100400.mp4

here is the full script. there is a couple thing that still need work but it is fully functioning except the issue I stated. I know my script favors one-liners and lacks adequate commenting. limiting the critique to my specified problem will be appreciated

$path= 'D:\Radio'
$site='mysite.com'
Set-Location -Path $path

if ($path -and $(Try{Test-Path $path.trim()} Catch{}) -and (((get-item $path).Attributes) -match "Directory")){
  $path = Get-Item $path
} else {Write-Host "mp2rss error: Invalid" ;exit}
$media = (Get-ChildItem -path $path -File -Recurse -Filter "*.mp*") | Where-Object `
{$_.extension -in ".mp3",".mp4"} | Sort-Object -Descending -Property LastWriteTime
if (!$media.Count){Write-Host "mp2rss error: No Media Found" ;exit}

# done verifying

function when_long($param){
  $param = ($param.AddMinutes(-($param.minute %5)).ToString("h:mmtt ddd, MMM d'th'") -replace
  '(?<!1)1th$','1st' -replace'(?<!1)2th$','2nd' -replace '(?<!1)3th$','3rd') -replace
  '(?<=:\d\d)AM','a' -replace '(?<=:\d\d)PM','p'
  return $param
}
function when_short($param){
  $param = ($param.ToString("ddd"),($param.AddMinutes(-($param.minute %5))).ToString("M/dd h:mmt").ToLower() -join " ")
  return $param
}

if ((Get-Volume -DriveLetter $path.ToString().Substring(0,1)).FileSystemLabel){
    $title_alt = (Get-Volume -DriveLetter $path.ToString().Substring(0,1)).FileSystemLabel
}else{$title_alt = "$env:COMPUTERNAME`($($path.FullName.Substring(0,1))`)"}

if ($path.FullName -eq $path.Root.FullName){
  $title = $title_alt
  $htm_out = "$($path)mpx.htm"
}else{
  $title = $path.BaseName
  $htm_out = "$path\mpx.htm"
}
$html = @("<html><head><title>$title</title></head><body>")

foreach ($folder in ((get-item $media).DirectoryName) | Select-Object -Unique){
  $ext = @('mp3','mp4')
  foreach ($ext in $ext){
    if ($media | Where-Object {$_.DirectoryName -in $folder -and $_.Extension -match $ext}){

      if ((Get-Item $folder).FullName -ne (Get-Item $folder).Root.FullName){
        $title = (Get-Item $folder).BaseName
      }else{$title = $title_alt}
      if ($ext -match 'mp4'){$title = "$title [V]"}
      $url = (([System.Uri]$folder).AbsoluteUri.TrimStart(([System.Uri]($path.ToString())).AbsoluteUri))
      if ($url){$url = "$site/$url/"}else{$url = "$site/"}
      $qty = (($media | Where-Object {$_.DirectoryName -in $folder -and $_.Extension -match $ext}) | Measure-Object).Count
      $whn = (when_long ($media | Where-Object {$_.DirectoryName -in $folder -and $_.Extension -match $ext} | Select-Object -First 1).CreationTime)

      $html += "<p><a href=`"http://$url$ext.rss`">$title</a><br>$qty files updated $whn</p>"
      $rss = @("<rss><channel><title>$title</title>")
      $rss += "<description>$qty files updated $whn</description>"

      foreach ($file in ($media | Where-Object {$_.DirectoryName -in $folder -and $_.Extension -match $ext})){
        if ($file.BaseName -match "^\d+$"){
          if ($file.BaseName -match "^\d{12}$"){
            $title = (when_short ([Datetime]::ParseExact($file.BaseName, 'yyyyMMddHHmm', $null)))
            $dsc = (when_long ([Datetime]::ParseExact($file.BaseName, 'yyyyMMddHHmm', $null)))
          }else{
            $title = (when_short $file.CreationTime)
            $dsc = (when_long $file.CreationTime)
          }
        }else{
          $title = $file.BaseName
          $dsc = (when_long $file.CreationTime)
        }
        $rss += "<item><title>$title</title>"
        $rss += "<description>$dsc</description>"
        $rss += "<enclosure url=`"http://$url$([uri]::EscapeDataString($file.Name))`"/></item>"
      }
      $rss += "</channel></rss>"
      $rss_out = "$folder\$ext.rss"
      if (-not (Test-Path $rss_out) -or (Compare-Object (Get-Content -Path $rss_out) $rss)){Out-File -InputObject $rss -FilePath $rss_out}
    }
  }
}
if (-not (Test-Path $htm_out) -or (Compare-Object (Get-Content -Path $htm_out) $html)){Out-File -InputObject $html -FilePath $htm_out}
mklement0
  • 382,024
  • 64
  • 607
  • 775
mrpaulc
  • 1
  • 1
  • 1
    You don't need `Get-Item` here, just do `foreach($folder in $media.DirectoryName |Sort-Object -Unique){...}` – Mathias R. Jessen Apr 20 '23 at 11:11
  • What account are you using for the Task? You have data in the C:\Users\jj9wo\mpxtorss.ps1 Only an admin or the user has access to the folder. – jdweng Apr 20 '23 at 12:06
  • thanks @MathiasR.Jessen, that solved this problem. of course, it uncovered or created others which I will explore I'm new here. how can I mark your response as a solution? – mrpaulc Apr 21 '23 at 11:19
  • 1
    @jdweng, I tried multiple configurations, turns out this was not my problem – mrpaulc Apr 21 '23 at 11:20
  • What account are you using for the task scheduler? You must specify an account. Default account is System which won't have the environment that you need to run code. – jdweng Apr 21 '23 at 12:27
  • @mrpaulc, now that we know what the problem was, it might help future readers if you remove incidental parts of your lengthy code snippet, so that it is easier to see where the problem occurred. – mklement0 Apr 21 '23 at 15:54
  • It also seems that the use of Task Scheduler is incidental to your problem. If you agree, I suggest removing references to it from the title and question body, so as not to attract future readers who do have an actual Task Scheduler problem. – mklement0 Apr 21 '23 at 16:10

1 Answers1

0

tl;dr

  • There is no need to use Get-Item with arguments that already are [System.IO.FileInfo] (or [System.IO.DirectoryInfo]) instances, such as output by Get-ChildItem.

    • Replace (Get-Item $media).DirectoryName with $media.DirectoryName
  • In code written for Windows PowerShell and in cross-edition code, extra effort is needed to use FileInfo / DirectoryInfo as strings:

    • For targeting cmdlets that accept file paths, pass FileInfo instances via the pipeline, which robustly binds FileInfo / DirectoryInfo instances by their full path, to the target cmdlets' -LiteralPath parameters - see this answer for background information; e.g.:

      # The [FileInfo] instance robustly binds to 
      # Select-String -LiteralPath by its full path.
      $someFileInfo | Select-String 'foo'
      
    • For targeting external programs / other contexts where you need a string representation of FileInfo instances' full paths, use their .FullName property explicitly; e.g.:

      cmd /c echo The full paths is: $someFileInfo.FullName
      

Read on for an explanation.


As Mathias R. Jessen points out, using Get-Item with $media is unnecessary, because $media already contains [System.IO.FileInfo] instances, due to containing the output from an earlier Get-ChildItem -File call.

Replacing (Get-Item $media).DirectoryName with $media.DirectoryName is not only simpler and more efficient, but actually implicitly solves your problem.

That is, Get-Item $media does not work as intended in Windows PowerShell:

  • The FileInfo instances are stringified in this call. (This part also applies in PowerShell (Core) 7+).

    • Unlike with pipeline input, when FileInfo instances are passed as arguments, they are converted to strings, because they bind to (positionally) implied -Path parameter, which is [string[]-typed.

    • As an aside: If you want to prevent accidental interpretation of paths you mean to be literal ones as wildcard expressions, you must use the -LiteralPath parameter, explicitly (when providing FileInfo / DirectoryInfo instances _via the pipeline, this parameter is implicitly bound).
      This matters for paths that contain [ and ] characters, given that they are metacharacters in PowerShell's wildcard language (for defining character ranges and sets).

  • In Windows PowerShell only, this stringification situationally results in only the name of the file (.Name) rather than its full path (.FullName) being used to obtain the string representation.

    • Fortunately, PowerShell (Core) 7+ (.NET (Core)) now consistently uses .FullName.
    • See this answer for background information.
  • Whether .Name or .FullName stringification occurs depends on the specifics of the Get-ChildItem call (see the linked answer), and in your case .Name-only stringification occurs.

    • Therefore, due to use of -Recurse, any FileInfo instances found in subdirectories of $path won't be found by Get-Item because the path information is missing due to the .Name stringification.

    • In the case at hand:

      • D:\Radio is your current location, due to the Set-Location $path call.

      • The recursive Get-ChildItem call then finds file D:\Radio\kmox\202303100400.mp4, for instance, and stores its FileInfo representation as part of the array in $media

      • In the Get-Item $media call, that file then stringifies as 202303100400.mp4 only (by .Name), which - in the absence of path information - Get-Item tries to resolve against the current location (directory).

      • That is, Get-Item looks for D:\Radio\202303100400.mp4 (missing kmox path component) - which doesn't exist, and causes the error you saw.

mklement0
  • 382,024
  • 64
  • 607
  • 775