Lennart Halldén
Lennart Halldén

Reputation: 33

I need my script to include the "LastWriteTime" property on the -Whatif output

I need to edit a script that I found here so that I can first see a report of the files it will delete including File name and path along with the "LastWriteTime" property so that I can analyze the output of the script for a couple of months before executing and before I configure it as a scheduled task:

I have tried playing around with the LastWriteTime object property for a while but I dont know what else to do..

Below is the code:

$limit = (Get-Date).AddDays(-30)

$del30 = "D:\contoso_ftp\users"

$ignore = Get-Content "C:\Users\username\Documents\Scripts\ignorelist.txt"

Get-ChildItem $del30 -Recurse | 

Where-Object {$_.LastWriteTime -lt $limit } |

Select-Object -ExpandProperty FullName |

Select-String -SimpleMatch -Pattern $ignore -NotMatch | 

Select-Object -ExpandProperty Line |

Remove-Item -Recurse -WhatIf

This is what the -Whatif output looks like so far:

What if: Performing the operation "Remove File" on target "D:\contoso_ftp\users\ftp-contosoaccount\contoso Downloads\contosofile.zip".

So.. as you can see I need to be able to get the "LastWriteTime" property in there.

Any help or pointers into articles or documentation is greatly appreciated.

Thanks in advance,

//Lennart

Upvotes: 3

Views: 835

Answers (3)

mklement0
mklement0

Reputation: 437953

As Mathias R. Jessen notes, you have no control over the target-object descriptions that cmdlets use with the common -WhatIf parameter, PowerShell's dry-run feature.

Here's a workaround, but note that it is suboptimal, mostly because use of a ForEach-Object call with per-object execution of a script block slows the command down noticeably:

'file1.txt', 'file2.txt' | # Stand-in for everything before your Remove-Item call
  Get-Item | # transform strings into [System.IO.FileInfo] objects
    ForEach-Object {
      $whatIf = $true # Set this to $false later to perform actual deletion.
      # Prepend the last-write timestamp *without a trailing newline*
      if ($whatIf) { Write-Host -NoNewline "$($_.LastWriteTime): " }
      # Let Remove-Item append its what-if message
      $_ | Remove-Item -WhatIf:$whatif
    }

The output will look something like this:

02/07/2021 15:44:24: What if: Performing the operation "Remove File" on target "/Users/jdoe/file1.txt".
02/07/2021 15:44:24: What if: Performing the operation "Remove File" on target "/Users/jdoe/file2.txt".

Note:

  • If you want to control the -WhatIf flag from outside your script, you have two options:

    • Either: Decorate your script's param(...) block with a [CmdletBinding(SupportsShouldProcess)] attribute to turn on -WhatIf common-parameter support:

      • Caveat: This will make calls to all cmdlets in your script that support -WhatIf apply it implicitly when you pass -WhatIf to your script, though you can selectively suppress that with -WhatIf:$false; see the alternative below for the inverse approach.

      • Since PowerShell automatically translates this common parameter into a local $WhatIfPreference preference variable, the code above must be adapted:

        • Use if ($WhatIfPreference) to test if -WhatIf was passed and Write-Host output should be produced.
        • Remove -WhatIf:$whatIf from the Remove-Item call - if -WhatIf is passed to your script, it will apply automatically
    • Or: If you want more control, use a custom [switch] $WhatIf parameter declaration instead of SupportsShouldProcess, and query $WhatIf / apply -WhatIf:$WhatIf explicitly and selectively.

      • In this case, simply remove the $whatIf = $true statement from the code above.
      • Caveat: Your script will then not respect the $WhatIfPreference variable, unless you explicitly check for it in case -WhatIf wasn't explicitly passed (check with $PSBoundParameters.ContainsKey('WhatIf')).
  • You can get as verbose as you like with the inline Write-Host call, but note that single-line output is only possible if you prepend the Write-Host output.

    • Alternatively, adapting Daniel's approach from a comment on the question, you can adapt the if statement above to bypass the Remove-Item -WhatIf call altogether if $whatIf is set to $true in the script block, and craft the full what-if message yourself:

      if ($whatif) { Write-Host ...  } else { $_ | Remove-Item }
      
  • Note that -WhatIf output can neither be captured nor suppressed - it always prints directly to the host (console).

Upvotes: 1

zett42
zett42

Reputation: 27766

You can't modify the output of Remove-Item when run with -WhatIf but you could wrap your pipeline in a function that supports -WhatIf and provides its own customized -WhatIf output:

function Test-Something {
    [CmdletBinding(SupportsShouldProcess)]    # This enables -WhatIf processing
    param (
        [string] $path,
        [datetime] $limit,
        [string] $ignore
    )
        
    Get-ChildItem $path -Recurse | 

    Where-Object { $_.LastWriteTime -lt $limit } |
        
    Select-Object -ExpandProperty FullName |
        
    Select-String -SimpleMatch -Pattern $ignore -NotMatch | 
        
    Select-Object -ExpandProperty Line |

    Get-ChildItem -Recurse | ForEach-Object {

        # If -WhatIf is passed, output the message and skip the deletion.
        # Else enter the if-block and delete the file.
        if( $PSCmdlet.ShouldProcess( 
                "Performing the operation `"Remove File`" on target `"$_`" (LastWriteTime: $($_.LastWriteTime))", 
                "Are you sure you want to delete file `"$_`" ?", 
                "Confirm" ) ) {
                
            Remove-Item $_ 
        }
    }  
}

Note that I didn't have to define the -WhatIf parameter within the function because it is added automatically by specifying SupportsShouldProcess. Now you can pass -WhatIf to your function to do a dry run:

Test-Something $del30 $limit $ignore -WhatIf

Sample output:

What if: Performing the operation "Remove File" on target "C:\test\file1.txt" (LastWriteTime: 12/30/2020 19:47:10)
What if: Performing the operation "Remove File" on target "C:\test\file2.txt" (LastWriteTime: 01/12/2021 00:59:59)

I've used the $PSCmdlet.ShouldProcess overload that takes three arguments to provide a detailed -WhatIf message. The other two arguments provide a message and a headline for the related -Confirm argument, which is also supported by the function:

Test-Something $del30 $limit $ignore -Confirm

Now PowerShell will ask before deletion:

Confirm
Are you sure you want to delete file "C:\test\file1.txt" ?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Yes"): 

Another cool feature you get for free is the verbose output. When you call the function with -Verbose the message supplied to $PSCmdlet.ShouldProcess() will be used as the verbose output:

Test-Something $del30 $limit $ignore -Verbose

Output:

VERBOSE: Performing the operation "Remove File" on target "C:\test\file1.txt" (LastWriteTime: 12/30/2020 19:47:10)
VERBOSE: Performing the operation "Remove File" on target "C:\test\file2.txt" (LastWriteTime: 01/12/2021 00:59:59)

Further reading: Everything you wanted to know about ShouldProcess

Upvotes: 4

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174505

As you've found, the output from -WhatIf is rather terse - "Action: Thing" - nothing more.

You can solve this in a script by separating reporting and actual removal:

param(
  [string]$Path = "D:\banqsoft_ftp\users",

  [datetime]$Limit = $((Get-Date).AddDays(-30)),

  [string[]]$ignore = $(Get-Content "$env:USERPROFILE\Documents\Scripts\ignorelist.txt"),

  [switch]$Force
)

$targetPaths = Get-ChildItem $del30 -Recurse |
  Where-Object {$_.LastWriteTime -lt $limit } |
  Select-Object -ExpandProperty FullName |
  Select-String -SimpleMatch -Pattern $ignore -NotMatch |
  Select-Object -ExpandProperty Line

# List files that would have been affected + their timestamps
Get-ChildItem $targetPaths -Recurse -File |Select-Object FullName,LastWriteTime

if($Force){
  # If -Force was specified then we remove as well
  $targetPaths |Remove-Item -Recurse -Force
}

Now you can run it as:

PS ~> .\script.ps1

... to just list the files with LastWriteTime, and once you're confident, run it with -Force:

PS ~> .\script.ps1 -Force

To actually remove the files

Upvotes: 1

Related Questions