SQL_Guy
SQL_Guy

Reputation: 353

Getting error when trying to navigate folder, subfolders and files

I have a powershell script, which is supposed to navigate folders and subfolders of a specified directory. Once at the lowest level (when there are no more sub-folders, but only files), it will process the files. For my example, let's assume I have the following folders and files:

c:\temp\filder1\file1.txt

c:\temp\filder2\file2.txt

c:\temp\filder3\file3.txt

The problem happens when I am trying to go from the lowest-level folder to processing files in that folder: $files = Get-ChildItem $folder. It seems that somehow the object folder is converted into just a string with the folder's name. I get the error, which is now using my user's default path with the folder name appended, which, of course, fails, because now such folder exists in my default path. The error is something like:

Get-ChildItem : Cannot find path 'C:\Users\my.name\Documents\Powershell\Powershell_Scripts\folder1' because it does not exist.

The path I would expect is 'c:\temp\folder1'

Here the simplified version of my script:

Param(
  [Parameter(Position = 1)] [string] $source_share_full_path = "c:\temp\"   # path to a top-level share
)

$folders = Get-ChildItem $source_share_full_path

#Loop through all the folders
ProcessAllSubfolders($folders)

function ProcessAllSubfolders($folderCollection) {
    foreach ($folder in $folderCollection)
    {
        if ($folder.Subfolders.Count -gt 0)
        {
            ProcessAllSubfolders($folder.SubFolders)
        }
        else
        {
            Write-Output "`nReady to process files in a folder : $folder.FullName `n " 
            $files = Get-ChildItem $folder.FullName
            ProcessFiles($files)
        }
    }
}

function ProcessFiles($files) {
    foreach ($file in $files)
    {
        Write-Output "`nReady to process file: $file `n  " 
    }
}

The credit for the method of navigating sub-folders belongs here

I appreciate any pointers!

Upvotes: 0

Views: 1231

Answers (2)

TessellatingHeckler
TessellatingHeckler

Reputation: 29048

As I commented, but am fleshing out a bit here:

  • folders don't seem to have a $folder.Subfolders property, so the count of subfolders will never work as intended.
  • You're calling the functions before defining them, which won't work properly (the first run fails with an unknown function, the next run will use the previous definition, and if you only run the script by invoking a new PowerShell session so the old definition doesn't stay in memory, it will never work)
  • Calling functions in PowerShell doesn't use () for the parameters, e.g. ProcessFiles($files) should be ProcessFiles $files
  • In the line $folders = Get-ChildItem $source_share_full_path you call the result $folders, but they aren't folders, they are files and folders mixed.
  • You're writing the log messages ("ready to process") into the object pipeline. That's probably not what you want to happen.

Unless you need to recurse down through the folders yourself, or plan to come back to the other folders later, how about something like:

Param(
  [Parameter(Position = 1)] [string] $root_path = "c:\temp\"
)


$folders = Get-ChildItem $root_path -Directory -Recurse |
               Where {$_.GetDirectories().Count -eq 0}


foreach ($folder in $folders) {

    $files = Get-ChildItem $folder.FullName -File
    foreach ($file in $files) {
        Write-Host "Ready to process file $($file.FullName)"
    }

}

Upvotes: 2

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174990

The SubFolders property in the example you reference is specific to SharePoint folders - it doesn't exist on FileSystemInfo objects, which is what Get-ChildItem returns from the file system provider.

You can use Get-ChildItem $Path -Directory to retrieve the subdirectories of $Path, and Get-ChildItem.

You might also want to add a CmdletBinding attribute to your functions and declare the parameters properly:

function ProcessAllSubfolders {

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [System.IO.DirectoryInfo[]]$FolderCollection
    )

    foreach ($Folder in $FolderCollection)
    {
        $SubFolders = @(Get-ChildItem $Folder.FullName -Directory)

        if ($SubFolders.Count -gt 0)
        {
            ProcessAllSubfolders -FolderCollection $SubFolders
        }
        else
        {
            Write-Verbose "Ready to process files in a folder : $($Folder.FullName)" 
            $Files = Get-ChildItem $folder.FullName
            ProcessFiles $files
        }
    }
}

function ProcessFiles {

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [System.IO.FileInfo[]]$Files
    )

    foreach ($File in $Files)
    {
        Write-Verbose "Ready to process file: $File"
        # Do your processing on $File here 
    }
}

Now you can call ProcessAllSubfolders with the -Verbose switch if you the messages written to the console:

$InitialFolders = Get-ChildItem $SourcePath -Directory

#Loop through all the folders
ProcessAllSubfolders -FolderCollection $InitialFolders

Save the script, and voila:

PS C:\> ProcessFiles.ps1 -Verbose
VERBOSE: Ready to process files in a folder : C:\temp\text\x
VERBOSE: Ready to process file: n_238633.log
VERBOSE: Ready to process file: n_895226.log

Upvotes: 2

Related Questions