How to skip folder when extracting file metadata?

28 views Asked by At

I have a script for extracting file metadata. I am trying to modify it so that it will skip a specified subfolder.

If you read the script, you can see there is a Param block, that includes options like "ExcludesFiles." I can get it to exclude a file, but I can't get it to exclude a folder.

Here is the script:

Function Get-FolderItem {
    
        [cmdletbinding(DefaultParameterSetName='Filter')]
        Param (
            [parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
            [Alias('FullName')]
            [string[]]$Path = $PWD,
            [parameter(ParameterSetName='Filter')]
            [string[]]$Filter = '*.*',    
            [parameter(ParameterSetName='Exclude')]
            [string[]]$ExcludeFile,              
            [parameter()]
            [int]$MaxAge,
            [parameter()]
            [int]$MinAge
        )
        Begin {
            $params = New-Object System.Collections.Arraylist
            $params.AddRange(@("/L","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/UNILOG:c:\temp\test.txt"))
            If ($PSBoundParameters['MaxAge']) {
                $params.Add("/MaxAge:$MaxAge") | Out-Null
            }
            If ($PSBoundParameters['MinAge']) {
                $params.Add("/MinAge:$MinAge") | Out-Null
            }
        }
        Process {
            ForEach ($item in $Path) {
                Try {
                    $item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
                    If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
                        Write-Warning ("{0} is not a directory and will be skipped" -f $item)
                        Return
                    }
                    If ($PSBoundParameters['ExcludeFile']) {
                        $Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile  -join ',')"
                    } Else {
                        $Script = "robocopy `"$item`" NULL $Filter $params"
                    }
                    Write-Verbose ("Scanning {0}" -f $item)
                    Invoke-Expression $Script | Out-Null
                    get-content "c:\temp\test.txt" | ForEach {
                        Try {
                            If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
                               $object = New-Object PSObject -Property @{
                                    FullName = $matches.FullName
                                    #Extension = $matches.fullname -replace '.*\.(.*)','$1'
                                    #FullPathLength = [int] $matches.FullName.Length
                                    #FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
                                    #Created = ([System.IO.FileInfo] $matches.FullName).creationtime
                                    #Created = ([System.IO.FileInfo] "\\?\$($matches.FullName)").creationtime
                                    #LastWriteTime = ([System.IO.FileInfo] "\\?\$($matches.FullName)").LastWriteTime
                                    #Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
                                    #Size = ([System.IO.FileInfo] "\\?\$($matches.FullName)").length
                                    #Access = ((Get-Acl -LiteralPath "\\?\$($matches.FullName)") |Select -Expand AccessToString)-replace '[\r\n]',' '
                                    #$permission = (Get-Acl $Folder).Access | ?{$_.IdentityReference -match $User} | Select IdentityReference,FileSystemRights
                                    #Owner = (Get-ACL $matches.Fullname).Owner
                                } 
                                $object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
                                Write-Output $object
                            } Else {
                                Write-Verbose ("Not matched: {0}" -f $_)
                            }
                        } Catch {
                            Write-Warning ("{0}" -f $_.Exception.Message)
                            Return
                        }
                    }
                } Catch {
                    Write-Warning ("{0}" -f $_.Exception.Message)
                    Return
                }
            }
        }
    }
    
  $a = Get-FolderItem "C:\Temp\ExtractMetadata" | Export-Csv -Path C:\Temp\2023-12-12Testing.csv -Encoding Unicode

I have tried the following, but the specified folder is not skipped:

 $a = Get-FolderItem "C:\Temp\ExtractMetadata" -ExcludeFile "C:\Temp\ExtractMetadata\Folder_to_Skip\" | Export-Csv -Path C:\Temp\2023-12-12Testing.csv -Encoding Unicode

I also tried following, but the output file is blank:

 $a = Get-FolderItem "C:\Temp\ExtractMetadata" -Filter "‪C:\Temp\ExtractMetadata\Folder to Skip\*.*" | Export-Csv -Path C:\Temp\2023-12-12Testing.csv -Encoding Unicode

I thought it might be based on Get Child Item (which has a built in excludes parameter) but I don't think this script uses Get-Child Item - it's just a loop. This answer indicates that what I need is a "continue" command, but where do I put it in? I'm unfamiliar with PowerShell syntax.

2

There are 2 answers

2
Doug Maurer On BEST ANSWER

You need to implement the /XD robocopy parameter. I found with using relative paths, what worked was to add them individually (multiple /XD,/XF entries.) There are several other improvements that could be made to this function.

Function Get-FolderItem {

    [cmdletbinding(DefaultParameterSetName='Filter')]
    Param (
        [parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [Alias('FullName')]
        [string[]]$Path = $PWD,
        [parameter()]
        [string[]]$Filter = '*.*',    
        [parameter()]
        [string[]]$ExcludeFile,      
        [parameter()]
        [string[]]$ExcludeFolder,               
        [parameter()]
        [int]$MaxAge,
        [parameter()]
        [int]$MinAge
    )
    Begin {
        $params = New-Object System.Collections.Arraylist
        $params.AddRange(@("/L","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/UNILOG:c:\temp\test.txt"))
        If ($PSBoundParameters['MaxAge']) {
            $params.Add("/MaxAge:$MaxAge") | Out-Null
        }
        If ($PSBoundParameters['MinAge']) {
            $params.Add("/MinAge:$MinAge") | Out-Null
        }
    }
    Process {
        ForEach ($item in $Path) {
            Try {
                $item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
                If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
                    Write-Warning ("{0} is not a directory and will be skipped" -f $item)
                    Return
                }

                If ($PSBoundParameters['ExcludeFile']) {
                    foreach($excluded in $ExcludeFile){
                        $params.Add("/XF `"$excluded`"") | Out-Null
                    }
                }

                If ($PSBoundParameters['ExcludeFolder']) {
                    foreach($excluded in $ExcludeFolder){
                        $params.Add("/XD `"$excluded`"") | Out-Null
                    }
                }

                $Script = "robocopy `"$item`" NULL $Filter $params"
                
                Write-Verbose ("Scanning {0}" -f $item)
                Invoke-Expression $Script | Out-Null
                get-content "c:\temp\test.txt" | ForEach {
                    Try {
                        If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
                            $object = New-Object PSObject -Property @{
                                FullName = $matches.FullName
                                #Extension = $matches.fullname -replace '.*\.(.*)','$1'
                                #FullPathLength = [int] $matches.FullName.Length
                                #FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
                                #Created = ([System.IO.FileInfo] $matches.FullName).creationtime
                                #Created = ([System.IO.FileInfo] "\\?\$($matches.FullName)").creationtime
                                #LastWriteTime = ([System.IO.FileInfo] "\\?\$($matches.FullName)").LastWriteTime
                                #Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
                                #Size = ([System.IO.FileInfo] "\\?\$($matches.FullName)").length
                                #Access = ((Get-Acl -LiteralPath "\\?\$($matches.FullName)") |Select -Expand AccessToString)-replace '[\r\n]',' '
                                #$permission = (Get-Acl $Folder).Access | ?{$_.IdentityReference -match $User} | Select IdentityReference,FileSystemRights
                                #Owner = (Get-ACL $matches.Fullname).Owner
                            } 
                            $object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
                            Write-Output $object
                        } Else {
                            Write-Verbose ("Not matched: {0}" -f $_)
                        }
                    } Catch {
                        Write-Warning ("{0}" -f $_.Exception.Message)
                        Return
                    }
                }
            } Catch {
                Write-Warning ("{0}" -f $_.Exception.Message)
                Return
            }
        }
    }
}
0
oymonk On

This script is based on Robocopy. Robocopy allows you skip folders, as described here.

Replace /XF in your script with /XD, and use the line $a = Get-FolderItem "C:\Temp\ExtractMetadata" -ExcludeFile "C:\Temp\ExtractMetadata\Folder_to_Skip\" | Export-Csv -Path C:\Temp\2023-12-12Testing.csv -Encoding Unicode.