.netpowershellperformanceget-childitem

How to fix/speed up powershell script?


I know there is code review for 'speed up' exists, but I also need to 'fix' problem of my script.

I migrated from Command Prompt(.bat) to Powershell(.ps1) because I think Command Prompt is hard to make script for complex things. I've heard that Powershell may have some overhead than Command Prompt, but if it is fast enough, I don't care.

Here is link for two files, sdn.old.bat and sdn.new.ps1. I put them in paste site because those are long enough.

paste.gg

Here is my problem.

This part takes a very long time to run.

$logs_loc = @(
    "$Env:LocalAppdata"
    "$Env:Appdata"
)
ForEach ($item in $logs_loc) {
    Get-ChildItem -Path "$item\*" -Recurse -Force -Include *.log *.log.txt | Remove-Item -Force
}

It takes ~5 seconds and I don't know why. This also doesn't do anything. This code should remove all *.log or *.log.txt files under %Appdata% and %LocalAppdata%, but it don't delete anything. I tested with test file randomly placed blank *.log and *.log.txt but they remain after run.

I haven't tested other part of my script, so there may another problem exists...


TL;DR

  1. I don't know why my code doesn't work but also takes too much time for running.
  2. Is there anything that can 'improve' the speed?

Solution

  • Otter's helpful answer already explains the issue with your current code, the -Include parameter takes string[] (string array) as argument, if you want to pass multiple filters to the parameter you need to separate them by a comma ,. See about_Arrays for details.

    As for improving the efficiency of your code, you would need to make .NET API calls to IO.Directory as zett42 points out in a comment or IO.DirectoryInfo, both options are valid however the latter outputs IO.FileInfo instead of strings. For handling the folder recursion you can use a Queue<T> instance:

    $env:LocalAppdata, $env:Appdata | & {
        [CmdletBinding()]
        param(
            [Parameter(ValueFromPipeline)]
            [string] $Path
        )
    
        begin { $queue = [Collections.Generic.Queue[IO.DirectoryInfo]]::new() }
        process { $queue.Enqueue($Path) }
        end {
            while($queue.Count) {
                $dir = $queue.Dequeue()
                foreach($filter in '*.log', '*.log.txt') {
                    $dir.EnumerateFiles($filter)
                }
                foreach($i in $dir.EnumerateDirectories()) {
                    $queue.Enqueue($i)
                }
            }
        }
    } -ErrorAction SilentlyContinue | Remove-Item -Force
    

    In .NET Core / PowerShell Core 7+, this task simplifies a lot thanks to the EnumerationOptions Class which allows us to Ignore Inaccessible files and folders:

    # IgnoreInaccessible is set to `$true` by Defaut.
    $enum = [IO.EnumerationOptions]@{
        RecurseSubdirectories = $true
        AttributesToSkip      = 'Hidden, System, SparseFile, ReparsePoint'
    }
    
    $env:LocalAppdata, $env:Appdata | ForEach-Object {
        foreach($filter in '*.log', '*.log.txt') {
            [IO.Directory]::EnumerateFiles($_, $filter, $enum)
        }
    } | Remove-Item -Force