none
Using PowerShell Start-Job to Start PowerShell Function with Parameters RRS feed

  • Question

  • Apologies in advance if I'm asking this question in the wrong place (and a pointer in the right direction would be great, if needed).

    I want a PowerShell function to search from a starting path (like D:\*), and find/remove all the files with a particular extension (like *.java). If I call Remove-FilesWithSpecifiedExtension below, it works as I expected.

    If there are a lot of files to find and remove, this leaves the user wondering what is going on while the code executes. So I tried to wrap the call with a Start-Job call (see Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor below), where dots get printed to the screen until the called function completes. The problem is that Start-Job never actually calls the function.

    If it matters, all the code below is in a single .ps1 file - I'm running PowerShell v5.1. I'm doing all this from within Visual Studio Code.

    Thanks...

    Function Remove-FilesWithSpecifiedExtension {
        <#
            .SYNOPSIS
            Delete all files in direcotry tree with specified $fileExtension
            starting at $startingDirectory.
        #>
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,
            [Parameter(Mandatory = $true)]
            [string]$Extension
        )

        Write-Output "Inside Remove-FilesWithSpecifiedExtension, $Path, $Extension"

        $errorFlag = $false

        $foundExtension = [System.IO.Path]::GetExtension($Extension)

        # Make sure the startingDirectory is a directory.
        # Make sure the file extension is in file extension format.
        # Absolutely make sure the fileExtension is *.java, NOT just .java with no asterisk.
        if ((Test-Path $Path) -And $Path.EndsWith("*")) {
            if (($null -ne $foundExtension) -And $Extension.StartsWith("*")) {
                Write-Output "'$Path' and '*$Extension'"
                $status = Get-ChildItem $Path -Recurse -Force -Include $Extension | #-ErrorAction SilentlyContinue | 
                    Remove-Item -Force
                Write-Output "Status: $status"
            }
            else {
                $errorFlag = $true
            }
        }
        else {
            $errorFlag = $true
        }
        if ($true -eq $errorFlag) {
            Write-Output("Usage: Remove-FilesWithSpecifiedExtension -Path 'D:\*' -Extension '*.java'")
            Write-Output("Note that asterisks on -Path and -Extension must be present for this function to work.")
        }
    }

    Function Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor {
        <#
            .SYNOPSIS
            Delete all files in direcotry tree with specified $fileExtension
            starting at $startingDirectory.
        #>
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,
            [Parameter(Mandatory = $true)]
            [string]$Extension
        )

        Write-Output "Starting search at $Path to remove all $Extensions files."

        # Wrap Remove-FilesWithSpecifiedExtension call in code that shows dots while
        # search in progress.
        $job = Start-Job -ScriptBlock { Remove-FilesWithSpecifiedExtension } -ArgumentList @($Path, $Extension)
        while (($job.State -eq "Running") -and ($job.State -ne "NotStarted")) {
            Write-Host '.' -NoNewline
            Start-Sleep -Seconds 1
        }
        Write-Host ' '
    }

    Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor -Path 'D:\*' -Extension '*.java'


    Randy

    Thursday, July 26, 2018 8:53 PM

Answers

  • This was not easy to figure out. The following line passes a function CALL to ScriptBlock, which is wrong:

    $job = Start-Job -ScriptBlock { Remove-FilesWithSpecifiedExtension } -ArgumentList @($Path, $Extension)

    Instead, -ScriptBlock needs a function REFERENCE to work:

    $job = Start-Job -ScriptBlock ${function:Remove-FilesWithSpecifiedExtension} -ArgumentList $Path, $Extension

    I'm re-pasting all the code, as there was also a typo in a debugging statement in the original post.


    Function Remove-FilesWithSpecifiedExtension {
        <#
            .SYNOPSIS
            Delete all files in direcotry tree with specified $fileExtension
            starting at $startingDirectory.
        #>
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,
            [Parameter(Mandatory = $true)]
            [string]$Extension
        )

        $errorFlag = $false

        $foundExtension = [System.IO.Path]::GetExtension($Extension)

        # Make sure the startingDirectory is a directory.
        # Make sure the file extension is in file extension format.
        # Absolutely make sure the fileExtension is *.java, NOT just .java with no asterisk.
        if ((Test-Path $Path) -And $Path.EndsWith("*")) {
            if (($null -ne $foundExtension) -And $Extension.StartsWith("*")) {
                Write-Output "'$Path' and '$Extension'"
                $status = Get-ChildItem $Path -Recurse -Force -Include $Extension -ErrorAction SilentlyContinue |
                    Remove-Item -Force
            }
            else {
                $errorFlag = $true
            }
        }
        else {
            $errorFlag = $true
        }
        if ($true -eq $errorFlag) {
            Write-Output("Usage: Remove-FilesWithSpecifiedExtension -Path 'D:\*' -Extension '*.java'")
            Write-Output("Note that asterisks on -Path and -Extension must be present for this function to work.")
        }
    }


    Function Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor {
        <#
            .SYNOPSIS
            Delete all files in direcotry tree with specified $fileExtension
            starting at $startingDirectory.
            This is actually a wrapper for the Remove-FilesWithSpecifiedExtension function.
            This wrapper prints a dot every second while searching the directory tree.
            Without this, the user thinks the script died when search large drives with
            if or no files found.
        #>
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,
            [Parameter(Mandatory = $true)]
            [string]$Extension
        )

        Write-Output "Starting search at $Path to remove all $Extension files."

        $job = Start-Job -ScriptBlock ${function:Remove-FilesWithSpecifiedExtension} -ArgumentList $Path, $Extension
        while (($job.State -eq "Running") -and ($job.State -ne "NotStarted")) {
            Write-Host '.' -NoNewline
            Start-Sleep -Seconds 1
        }

        Write-Output "Completed search at $Path to remove all $Extension files."
    }

    Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor -Path 'D:\*' -Extension '*.java'


    Randy

    Friday, July 27, 2018 4:46 PM

All replies

  • Just send the script to the job.

    Start-Job -FilePath yourscript.ps1

    You don't need a function:

    Get-ChildItem d:\test\* -include *.java -File  -Recurse | Remove-Item -Verbose

    This will echo all files being removed to the screen.  This will keep the user entertained.

     


    \_(ツ)_/


    • Edited by jrv Thursday, July 26, 2018 9:02 PM
    Thursday, July 26, 2018 9:01 PM
  • You can also just do this:

    Remove-Item d:\test\* -Include *.java -Recurse -Verbose


    \_(ツ)_/

    Thursday, July 26, 2018 9:04 PM
  • This was not easy to figure out. The following line passes a function CALL to ScriptBlock, which is wrong:

    $job = Start-Job -ScriptBlock { Remove-FilesWithSpecifiedExtension } -ArgumentList @($Path, $Extension)

    Instead, -ScriptBlock needs a function REFERENCE to work:

    $job = Start-Job -ScriptBlock ${function:Remove-FilesWithSpecifiedExtension} -ArgumentList $Path, $Extension

    I'm re-pasting all the code, as there was also a typo in a debugging statement in the original post.


    Function Remove-FilesWithSpecifiedExtension {
        <#
            .SYNOPSIS
            Delete all files in direcotry tree with specified $fileExtension
            starting at $startingDirectory.
        #>
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,
            [Parameter(Mandatory = $true)]
            [string]$Extension
        )

        $errorFlag = $false

        $foundExtension = [System.IO.Path]::GetExtension($Extension)

        # Make sure the startingDirectory is a directory.
        # Make sure the file extension is in file extension format.
        # Absolutely make sure the fileExtension is *.java, NOT just .java with no asterisk.
        if ((Test-Path $Path) -And $Path.EndsWith("*")) {
            if (($null -ne $foundExtension) -And $Extension.StartsWith("*")) {
                Write-Output "'$Path' and '$Extension'"
                $status = Get-ChildItem $Path -Recurse -Force -Include $Extension -ErrorAction SilentlyContinue |
                    Remove-Item -Force
            }
            else {
                $errorFlag = $true
            }
        }
        else {
            $errorFlag = $true
        }
        if ($true -eq $errorFlag) {
            Write-Output("Usage: Remove-FilesWithSpecifiedExtension -Path 'D:\*' -Extension '*.java'")
            Write-Output("Note that asterisks on -Path and -Extension must be present for this function to work.")
        }
    }


    Function Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor {
        <#
            .SYNOPSIS
            Delete all files in direcotry tree with specified $fileExtension
            starting at $startingDirectory.
            This is actually a wrapper for the Remove-FilesWithSpecifiedExtension function.
            This wrapper prints a dot every second while searching the directory tree.
            Without this, the user thinks the script died when search large drives with
            if or no files found.
        #>
        param(
            [Parameter(Mandatory = $true)]
            [string]$Path,
            [Parameter(Mandatory = $true)]
            [string]$Extension
        )

        Write-Output "Starting search at $Path to remove all $Extension files."

        $job = Start-Job -ScriptBlock ${function:Remove-FilesWithSpecifiedExtension} -ArgumentList $Path, $Extension
        while (($job.State -eq "Running") -and ($job.State -ne "NotStarted")) {
            Write-Host '.' -NoNewline
            Start-Sleep -Seconds 1
        }

        Write-Output "Completed search at $Path to remove all $Extension files."
    }

    Remove-FilesWithSpecifiedExtensionShowDotsWaitCursor -Path 'D:\*' -Extension '*.java'


    Randy

    Friday, July 27, 2018 4:46 PM