34

I'm trying to use robocopy inside powershell to mirror some directories on my home machines. Here's my script:

param ($configFile)

$config = Import-Csv $configFile
$what = "/COPYALL /B /SEC/ /MIR"
$options = "/R:0 /W:0 /NFL /NDL"
$logDir = "C:\Backup\"

foreach ($line in $config)
{
 $source = $($line.SourceFolder)
 $dest = $($line.DestFolder)
 $logfile =  $logDIr 
 $logfile += Split-Path $dest -Leaf
 $logfile += ".log"

 robocopy "$source $dest $what $options /LOG:MyLogfile.txt"
}

The script takes in a csv file with a list of source and destination directories. When I run the script I get these errors:

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

  Started : Sat Apr 03 21:26:57 2010

   Source : P:\ C:\Backup\Photos \COPYALL \B \SEC\ \MIR \R:0 \W:0 \NFL \NDL \LOG:MyLogfile.txt\
     Dest - 

    Files : *.*

  Options : *.* /COPY:DAT /R:1000000 /W:30 

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

ERROR : No Destination Directory Specified.

       Simple Usage :: ROBOCOPY source destination /MIR

             source :: Source Directory (drive:\path or \\server\share\path).
        destination :: Destination Dir  (drive:\path or \\server\share\path).
               /MIR :: Mirror a complete directory tree.

    For more usage information run ROBOCOPY /?


****  /MIR can DELETE files as well as copy them !

Any idea what I need to do to fix?

Thanks, Mark.

Mark Allison
  • 2,098
  • 7
  • 26
  • 45

8 Answers8

28

Populating strings into parameters for external commands from within Powershell requires some hoop jumping if you want to be able to use variable expansion and also have the resulting command line properly understand which parameters you want to be separated and which should not. In your example you are sending the entire string as the first parameter and Robocopy is telling you that it can't find that long string as a source directory. You do want the entire Source string to be treated as one param (including spaces that may be in there), likewise for Destination but your $what and $options will fail because they will both be delivered to Robocopy as a single parameter which cannot be parsed.

There are a few ways to do this right but the following snippet is based on the way you seem to want to break out your parameters and does work for the single directory example I've used.

$source="C:\temp\source"
$dest="C:\temp\dest"

$what = @("/COPYALL","/B","/SEC","/MIR")
$options = @("/R:0","/W:0","/NFL","/NDL")

$cmdArgs = @("$source","$dest",$what,$options)
robocopy @cmdArgs
Helvick
  • 19,579
  • 4
  • 37
  • 55
  • Thanks for the reply. I've tried your code but I still get the following error: ERROR : Invalid Parameter #3 : "/COPYALL /B /SEC /MIR" – Mark Allison Apr 03 '10 at 22:18
  • When I run the above stub code (copied from above) it works exactly as expected - specifically all the options\what items are recognised as separate parameters. If you run just the above stub (with a source folder that exists) does it work? If so then double check the Quotes\escaping in your own modified code. The invalid parameter #3 indicates that your version of $what is still a single string not an array of separate parameters in the code you are running. – Helvick Apr 03 '10 at 22:56
  • 3
    +1. That's definitely the issue here. I find it funny how people constantly try to use PowerShell as if it were an old text-only-based shell ;-) – Joey Nov 24 '10 at 13:33
  • @Joey Have you found the objects model of powershell to work out better in practice than the text based ones? – Didier A. Jan 24 '16 at 23:20
  • 1
    @DidierA.: Definitely. Especially where objects exist to describe the things you interact with, e.g. files, processes, services, etc. But even when you only have native applications and string outputs you can just use the usual string operators, e.g. `-match` and `-replace` as well as filtering/projection cmdlets to work with that. It's generally quite well-designed in that regard (most `*-Object` commands are orthogonal and they all can be applied to any object). – Joey Jan 25 '16 at 13:11
15

If the variables for $what and $options don't change, why are they variables?

$what = "/COPYALL /B /SEC /MIR" 
$options = "/R:0 /W:0 /NFL /NDL" 

This works fine for me:

robocopy   $source $dest /COPYALL /B /SEC /MIR /R:0 /W:0 /NFL /NDL
  • Good point - keep it simple. :) – Helvick Apr 04 '10 at 01:31
  • 3
    also /sec not /sec/ is critical here –  Apr 04 '10 at 01:40
  • 1
    So you just skipped the /Log and it worked :-) Gotcha is that you get strange Errors calling Robocopy with PS when the Logfile does not exist beforehand. – icnivad Apr 26 '13 at 05:32
  • This does not work when $dest is an array (e.g. robocopy $source $dest[$I] /MIR /Z) will fail. – Zian Choy Apr 29 '14 at 22:54
  • 7
    I know this is an old post, but I think its worth saying that having these as variables is a signal to future readers of the script that these were meant to be configurable parts of the script. The reason this works is that it happens to fix the typo in the original $what. – Octopus Sep 27 '16 at 05:42
  • 3
    Even if something isn't changing, variable encapsulation can help to make a script less convoluted. And if it turns out I need to reference that variable again later on, it makes the script more dynamic – Kellen Stuart Dec 29 '16 at 18:15
3

Remove the quotes from the robocopy line?

i.e.

param ($configFile)

$config = Import-Csv $configFile
$what = "/COPYALL /B /SEC/ /MIR"
$options = "/R:0 /W:0 /NFL /NDL"
$logDir = "C:\Backup\"

foreach ($line in $config)
{
 $source = $($line.SourceFolder)
 $dest = $($line.DestFolder)
 $logfile =  $logDIr 
 $logfile += Split-Path $dest -Leaf
 $logfile += ".log"

 robocopy $source $dest $what $options /LOG:MyLogfile.txt
}
Bryan
  • 7,538
  • 15
  • 68
  • 92
  • No I tried that first and it didn't work (similar errors), so I changed it to what is shown above. Any other ideas? – Mark Allison Apr 03 '10 at 20:02
  • What's the output when you run it without quotes? Your original output showing the Source as "P:\ C:\Backup\Photos \COPYALL \B \SEC\ \MIR \R:0 \W:0 \NFL \NDL \LOG:MyLogfile.txt\" was what drew my attention to the quotes. – Bryan Apr 03 '10 at 20:34
3

I had the same problem. The whole command line was interpreted as the first argument.

Nothing mentioned above worked including:

invoke-expression "robocopy ""$sourceFolder"" ""$destinationFolder"" /MIR"  
invoke-expression "robocopy \`"$sourceFolder\`" \`"$destinationFolder\`" /MIR"  

Leaving out the quotes doesn't work when there's a space in the path:

invoke-expression "robocopy $sourceFolder $destinationFolder /MIR"  

After trying the tricks in http://edgylogic.com/blog/powershell-and-external-commands-done-right/, I finally got it.

robocopy "\`"$sourceFolder"\`" "\`"$destinationFolder"\`" /MIR

Maybe I should have stuck with bat files. :)

splattne
  • 28,348
  • 19
  • 97
  • 147
Justin
  • 31
  • 2
2

I have found that the best way to execute a native command is to use the & command to execute a list of strings. Also if you want the console output to be included in a powershell transcript you will need to pipe the output to out-host. Here is an example of calling 7Zip with multiple parameters taken from a 7-Zip to Amazon S3 Powershell Script that I wrote:

$7ZipPath = "C:\Program Files\7-Zip\7z.exe" #Path to 7Zip executable
$FolderPath = "C:\Backup" #Folder to backup (no trailing slash!)
$FolderName = $FolderPath.Substring($FolderPath.LastIndexOf("\")+1) #grab the name of the backup folder
$TimeStamp = [datetime]::Now.ToString("yyyy-MM-dd_HHmm") #Create unique timestamp string
$strFile = [String]::Format("{0}\{1}_{2}.7z", "c:\",$FolderName,$TimeStamp) #Create filename for the zip
#7z options: add, type 7z, Archive filename, Files to add (with wildcard. Change \* to \prefix* or \*.txt to limit files), compression level 9, password, encrypt filenames, Send output to host so it can be logged.
& $7ZipPath "a" "-t7z" "$strFile" "$FolderPath\*" "-mx9" "-r" "-pPASSWORD" "-mhe" | out-host #create archive file
if($LASTEXITCODE -ne 0){ #Detect errors from 7Zip. Note: 7z will crash sometimes if file already exists
    Write-Error "ERROR: 7Zip terminated with exit code $LASTEXITCODE. Script halted."
    exit $LASTEXITCODE
}

Basically for your case I would try something like this (may need to break out the $what and $options parameters individually):

$robocopypath = "c:\pathtofolder\robocopy.exe"
& $robocopypath "$source" "$dest" "$what" "$options" "/LOG:MyLogfile.txt"
Greg Bray
  • 5,530
  • 5
  • 33
  • 52
  • The problem with this is that as you say "$what" and "$options" will get sent to robocopy as single parameters rather than a list of separate ones and given his sample code they will have to be broken out, there's no may about it. – Helvick Apr 04 '10 at 19:34
  • You can use the splat operator `@` if you are using powershell v3 to split a comma separated list into an argument stack – Zasz Sep 08 '12 at 04:21
1

This worked for me and allows for a dynamic input of the log file location. the & symbol just tells PowerShell that the text is a line of code I want to run.

    $source = "E:\BackupToRestore\"
    $destination = "E:\RestoreMe\"
    $logfile = "E:\robocopyLogFile.txt"    
    $switches = ("/B", "/MIR", "/XJ", "/FFT", "/R:0", "/LOG:$logfile")
    & robocopy $source $destination $roboCopyString $switches
BriGuy
  • 11
  • 2
1

Adding my 2 cents to this as it may help someone... The below code is missing the "loop" part where I read a csv to get the files to copy

# first we setup an array of possible robocopy status
$RobocopyErrors="NO ERRORS",
            "OKCOPY",
            "XTRA",
            "OKCOPY + XTRA",
            "MISMATCHES",
            "OKCOPY + MISMATCHES",
            "MISMATCHES + XTRA",
            "OKCOPY + MISMATCHES + XTRA",
            "FAIL",
            "OKCOPY + FAIL",
            "FAIL + XTRA",
            "OKCOPY + FAIL + XTRA",
            "FAIL + MISMATCHES& goto end",
            "OKCOPY + FAIL + MISMATCHES",
            "FAIL + MISMATCHES + XTRA",
            "OKCOPY + FAIL + MISMATCHES + XTRA",
            "***FATAL ERROR***"
#setting some variables with the date
$DateLogFile=get-date -format "yyyyddMMhh"

#this commands below one usually goes into a loop
$DateLog=get-date -format "yyyyddMMhhmmss"

#adding options and command arg as described in previous post
$options = @("/R:0","/W:0")
$cmdArgs = @("$Source","$Destination",$File,$options)

#executing Robocopy command
robocopy @cmdArgs

# We capture the lastexitcode of the command and use to confirm if all was good or not

$errorlevel=$LASTEXITCODE
# I output the status to a log file
"$DateLog`t::$($RobocopyErrors[$errorlevel])::`"$file`"`t`"$Source`" -> `"$Destination`"" | out-file "$scriptPath\Logs\$DateLogFile.log" -append
 if ($errorlevel -lt 8) {
    #error below 8 = all is good

}
else {
    # something bad happened ..  

}
galadriann
  • 11
  • 1
0
invoke-expression "robocopy $source $dest $what $options /LOG:MyLogfile.txt"

This will interpolate all of the variables, then call the resulting string. The way you have it now, it looks like robocopy is being invoked with the quotes around all of the options, i.e., robocopy "c:\src c:\dst blah meh". This is interpreted as a single parameter.

M Aguilar
  • 879
  • 5
  • 5