windowspowershellformatrobocopydry-run

Mangled formatting of ShouldProcess message


I'm trying to implement a -WhatIf flag in a RoboCopy script (swap user for your user profile):

[CmdletBinding(SupportsShouldProcess)]
param (
    $Purge = $false
)

$RCCommand = "Robocopy C:\Users\user\Desktop\Source C:\Users\user\Desktop\Target"
$WhatIfMessage = Invoke-Expression "$RCCommand /L"

if ($PSCmdlet.ShouldProcess($WhatIfMessage, $WhatIfMessage, 'Are you sure?')) {
    Invoke-Expression $RCCommand
}

This gives a barely readable output due to mangled formatting:

> .\Desktop\Invoke-RoboCopy.ps1 -WhatIf
What if:  -------------------------------------------------------------------------------    ROBOCOPY     ::     Robust File Copy for Windows                               -------------------------------------------------------------------------------    Started : Monday, June 2, 2025 19:47:55    Source : C:\Users\user\Desktop\Source\      Dest : C:\Users\user\Desktop\Target\      Files : *.*                Options : *.* /L /DCOPY:DA /COPY:DAT /R:1000000 /W:30   ------------------------------------------------------------------------------                      1    C:\Users\user\Desktop\Source\    *EXTRA Dir        -1     C:\Users\user\Desktop\Target\NotSource\   *EXTRA Dir        -1    C:\Users\user\Desktop\Target\Source1\  ------------------------------------------------------------------------------                 Total    Copied   Skipped  Mismatch    FAILED    Extras     Dirs :         1         0         1         0         0         2    Files :         1         0         1         0         0         0    Bytes :         0         0         0         0         0         0    Times :   0:00:00   0:00:00                       0:00:00   0:00:00    Ended : Monday, June 2, 2025 19:47:55

For comparison, here's a modified script that just outputs the message directly, bypassing -WhatIf followed by the output:

[CmdletBinding(SupportsShouldProcess)]
param (
    $Purge = $false
)

$RCCommand = "Robocopy C:\Users\user\Desktop\Source C:\Users\user\Desktop\Target"
$WhatIfMessage = Invoke-Expression "$RCCommand /L"
$WhatIfMessage
> .\Desktop\Invoke-RoboCopy.ps1

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

  Started : Monday, June 2, 2025 19:48:19
   Source : C:\Users\user\Desktop\Source\
     Dest : C:\Users\user\Desktop\Target\

    Files : *.*

  Options : *.* /L /DCOPY:DA /COPY:DAT /R:1000000 /W:30

------------------------------------------------------------------------------

                           1    C:\Users\user\Desktop\Source\
        *EXTRA Dir        -1    C:\Users\user\Desktop\Target\NotSource\
        *EXTRA Dir        -1    C:\Users\user\Desktop\Target\Source1\

------------------------------------------------------------------------------

               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :         1         0         1         0         0         2
   Files :         1         0         1         0         0         0
   Bytes :         0         0         0         0         0         0
   Times :   0:00:00   0:00:00                       0:00:00   0:00:00
   Ended : Monday, June 2, 2025 19:48:19

How do I get the -WhatIf message to look nice and neat like in the latter example?


Solution

  • Your issue is that the array of strings outputted by robocopy is getting converted into a single string when passed as argument of .ShouldProcess() and when this happens in PowerShell, every element of the array gets joined by the value of $OFS (a space by default). Simple solution is to use a cmdlet that will convert your array to a single string preserving its new-lines, like Out-String or alternatively, you can join them yourself using -join [Environment]::NewLine.

    As aside, you should probably avoid the use of Invoke-Expression when not needed, here is how the final code should look:

    [CmdletBinding(SupportsShouldProcess)]
    param (
        $Purge = $false
    )
    
    $command = 'Robocopy'
    $arguments = @(
        'C:\Users\user\Desktop\Source'
        'C:\Users\user\Desktop\Target')
    
    $WhatIfMessage = & $command ($arguments + '/L') | Out-String
    
    if ($PSCmdlet.ShouldProcess($WhatIfMessage, $WhatIfMessage, 'Are you sure?')) {
        & $command $arguments
    }