Powershell, robocopy array: invalid parameter & timestamp preserving - powershell

I have this Powershell code:
Function CheckFileList()
{
$limit = (Get-Date).AddDays(-270)
$input_path = gci '//network/sourceDir' | sort -property LastWriteTime
$output_file = 'c:\PowershellScripts\prune_results.txt'
#Clear-Content $output_file
$countf = 0
$outputstr = ""
$outputstr = $(Get-Date -format 'F') + " - Folders to be purged:`r`n"
$input_path | Foreach-Object{
if ( (Get-Item $_.FullName) -is [System.IO.DirectoryInfo] ) {
if ( $_.LastWriteTime -le $limit ) {
$source='//network/sourceDir' + $_.Name
$dest="\\computer\c$\targetDir" + $_.Name
$what=#("/MOVE")
$options=#("/COPY:DAT /DCOPY:T")
$cmdArgs = #("$source","$dest",$what,$options)
#"robocopy " + $cmdArgs >> $output_file
robocopy #cmdArgs
$outputstr = $outputstr + " (" + $_.LastWriteTime + ") `t" + $_.Name + "`r`n"
$countf++
$outputstr = $outputstr + "Folders [to be] purged: " + $countf + "`r`n`r`n"
$outputstr >> $output_file
Exit
}
}
}
$outputstr = $outputstr + "Folders [to be] purged: " + $countf + "`r`n`r`n"
$outputstr >> $output_file
}
CheckFilelist
This is intended to move many folders (and the files in them) while preserving the folder timestamps.
When I run it, I get this error:
-------------------------------------------------------------------------------
ROBOCOPY :: Robust File Copy for Windows
-------------------------------------------------------------------------------
Started : Mon Apr 27 13:20:35 2015
Source - \\network\sourceDir\someFolder12345\
Dest - \\computer\c$\someFolder12345\
Files :
Options : /COPY:DAT /MOVE /R:1000000 /W:30
------------------------------------------------------------------------------
ERROR : Invalid Parameter #4 : "/COPY:DAT /DCOPY:T"
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 !
Is there something wrong with my what/options array? The parameters look valid to me.
[EDIT] I'm also finding this script is not preserving folder timestamps. someFolder12345 ends up on the targetDir with the date/time of "now". The files within the folder are preserving timestamps, but not the folder?

It looks like your string "/COPY:DAT /DCOPY:T" is being passed to robocopy as one argument, not as 2 separate arguments. If you check the $options variable, it has a single item in the array. Try changing that line to $options=#("/COPY:DAT","/DCOPY:T") so that each argument is passed in separately.

Related

Re-run Batch File if 'Keyword' Exists

I want to create a script that re-runs a batch file if a specific keyword is found within a log. The problem that I am having is with the function used to check the file. Currently, when I run this script it exits saying that the expression "does not match" even though the keyword does, in fact, exist within the log file. In this case, the log file to check is named is 'output.log' and the keyword to match is called 'temporary'.
$current_date = Get-Date -Format yyyyMMdd
$file_path = "backup_" + $current_date
"checking in directory... --> " + $file_path
$word_to_find = "temporary"
$file_to_check = "output.log"
"Searching for matching expression '" + $word_to_find + "' in file: " + $file_to_check
$containsWord = $file_to_check | %{$_ -match $word_to_find}
if ($containsWord -contains $true) {
'The expression matches, re-running batch feed.'
start .\batch_script.bat
} else {
'The expression does not match. Feed OK.'
}
Thats because you first have to get the content of the log, atm you are just comparing the string "output.log" against the string "temporary", which ofc returns the value 'false'.
If you want to keep your attempt, try it like this (Remember that output.log should either be in the running directory (than use .\ like here) or you have to give the full path to the log file):
$current_date = Get-Date -Format yyyyMMdd
$file_path = "backup_"+ $current_date
"checking in directory... --> " + $file_path
$word_to_find="temporary"
$file_to_check=".\output.log"
"Searching for matching expression '" + $word_to_find + "' in file: " + $file_to_check
$containsWord = Get-Content $file_to_check | %{$_ -match $word_to_find}
If ($containsWord -contains $true) {
'The expression matches, re-running batch feed.'
start .\batch_script.bat
}
Else {
'The expression does not match. Feed OK.'
}
If you are up to some improvements, I would do it more like this:
$current_date = Get-Date -Format yyyyMMdd
$file_path = "backup_"+ $current_date
Write-Host "checking in directory... --> $file_path"
$word_to_find="temporary"
$file_to_check="<fullPathToLog>\output.log"
Write-Host "Searching for matching expression '" + $word_to_find + "' in file: $file_to_check"
If((Get-Content $file_to_check) -match $word_to_find) {
'The expression matches, re-running batch feed.'
start .\batch_script.bat
}
Else {
'The expression does not match. Feed OK.'
}
This would save you the foreach and the extra variable.

How do I correctly pass parameters to ImageMagick from PowerShell?

The following code works perfectly from the command line to combine two TIFF files.
magick -quiet file1.tif file2.tif -compress JPEG filecombined.tif
However, when I try to use it in PowerShell, I am getting many errors from Magick that indicate that it is not getting the correct parameters. My PowerShell code looks something like the following.
$InputFiles = 'files1.tif file2.tif'
$DestinationFile = 'filecombined.tif'
$magick -quiet $InputFiles -compress JPEG $DestinationFile
This gives me errors stating that it cannot find the input files and the message indicates that it thinks it is one filename instead of two. In PowerShell v4, I was able to get it to work by quoting each of the names. Not sure why this helped, but the names did not have spaces. However, I had to upgrade to v5 and this method broke.
I tried using a temporary file to store the input filenames, but this just caused a different error.
$InputFiles = 'files1.tif file2.tif'
$InputFiles | Out-File list.tmp
$DestinationFile = 'filecombined.tif'
$magick -quiet '#list.tmp' -compress JPEG $DestinationFile
magick.exe: unable to open image '#z:ÿþz
Put all the parameters for Magick into an array and use the call (&) operator to execute the command.
$MagickParameters = #( '-quiet' )
$MagickParameters += 'file1.tif'
$MagickParameters += 'file2.tif'
$MagickParameters += #( '-compress', 'JPEG' )
$MagickParameters += 'filecombined.tif'
&'magick' $MagickParameters
This may not be the most efficient use of arrays, but similar methods are possible if performance is a concern.
I had a large collection of EPS images in several folders that I had to convert to PNG. I tested many Image Conversion programs, but most could not handle recursive conversion of Vector to Raster without choking (most displayed errors after processing a limited number of files. Some could not convert recursively through many subfolders). I created the following Powershell script from various sources that solved my problem and made recursive conversion of many files and folders easier. You can modify the file to perform any ImageMagick functions you need.
Have Fun.
# Recursive-Convert-EPS-to-PNG.ps1
$srcfolder = "C:\Temp"
$destfolder = "C:\Temp"
$im_convert_exe = "convert.exe"
$src_filter = "*.eps"
$dest_ext = "png"
$options = "-depth 8 -colorspace gray -threshold 40% -alpha off"
$logfile = "C:\temp\convert.log"
$fp = New-Item -ItemType file $logfile -force
$count=0
foreach ($srcitem in $(Get-ChildItem $srcfolder -include $src_filter -recurse))
{
$srcname = $srcitem.fullname
# Construct the filename and filepath for the output
$partial = $srcitem.FullName.Substring( $srcfolder.Length )
$destname = $destfolder + $partial
$destname= [System.IO.Path]::ChangeExtension( $destname , $dest_ext )
$destpath = [System.IO.Path]::GetDirectoryName( $destname )
# Create the destination path if it does not exist
if (-not (test-path $destpath))
{
New-Item $destpath -type directory | Out-Null
}
# Perform the conversion by calling an external tool
$cmdline = $im_convert_exe + " `"" + $srcname + " `"" + $options + " `"" + $destname + " `""
#echo $cmdline
invoke-expression -command $cmdline
# Get information about the output file
$destitem = Get-item $destname
# Show and record information comparing the input and output files
$info = [string]::Format( "{0} `t {1} `t {2} `t {3} `t {4} `t {5}", $count, $partial, $srcname, $destname, $srcitem.Length , $destitem.Length)
echo $info
Add-Content $fp $info
$count=$count+1
}

Powershell incorrect parameter

So I am trying to pass the file directory to the function convert at the bottom. When I run the script I receive the output:
Succes
C:\test 2\00000027627-00001\PROCESSING CHECKLIST
convert : Invalid Parameter - 2\00000027627-00001\PROCESSING
At C:\Users\pmanca\Desktop\msbxmlreader.ps1:35 char:13
+ convert([string]$hostdirectoryPlusLoanPlusDocType)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (Invalid Paramet...0001\PROCESSING:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
It appears to be cutting off the file path after C:\test. Could the file path be too long? even so i wouldn't imagine getting the error at the function call and instead somewhere in the function when it couldn't resolve the path.
#? is a shortcut for where object and % is a shortcut for foreach-object
cls
#count 1 = Loan Number
#count 4 = Cust Num
#count 5 = Document Type
$hostdirectory = "C:\test 2"
$count = 0
$file = (Select-xml -Path "$hostdirectory\index.xml" -XPath / ).Node
$test = $file.ExportedResult.Docs.ExportedDoc.Doc.UdiValues.UdiValue.Value# | Select-Object {$_.UdiValue.Value.InnerXML} -Unique #|? {$_.UdiValue.Name -eq "Loan Number"}
$test | ForEach-Object{
$count++
# Write-Host $_.innerxml "----" $count
if($count -eq 1){
[string]$xmlHold = $_.InnerXML
$hostdirectoryPlusLoan = "$hostdirectory\$xmlHold"
if(!(test-path $hostdirectoryPlusLoan)){
New-Item $hostdirectoryPlusLoan -ItemType directory
}
}
if($count -eq 5){
[string]$xmlHold = $_.InnerXML
$hostdirectoryPlusLoanPlusDocType = "$hostdirectoryPlusLoan\$xmlHold"
if(!(test-path $hostdirectoryPlusLoanPlusDocType)){
New-Item $hostdirectoryPlusLoanPlusDocType -ItemType directory
}
if(Test-Path "$hostdirectory\$xmlhold.pdf"){
$check = Copy-Item "$hostdirectory\$xmlHold.pdf" -Destination $hostdirectoryPlusLoanPlusDocType -ErrorAction SilentlyContinue
if(-not $?) {write-warning "Copy Failed"; Write-Host $Error[0].exception.message}
else {write-host "Succes"}
Write-Host $hostdirectoryPlusLoanPlusDocType
convert([string]$hostdirectoryPlusLoanPlusDocType)
}
}
if($count -ge 8){
$count = 0
# Write-Host "-----------------------------------"
}
}
function convert([string]$inputDirectory){
write-host $inputDirectory
#Variable to hold current input directory
$InputPathFilter = $InputDirectory + '\*.pdf'
#Variable to hold the list of PDFs from the current input directory
$PDFList = #(gci $InputPathFilter | foreach {write-output $_.name})
#Loop through list of PDF files to convert to TIF image files.
for ($j=0; $j -lt $PDFList.count; $j++) {
#Each PDF will go into its own directory/batch
#Create a variable with only the file name
$FileName = $PDFList[$j] -replace(".pdf",'')
#Variable of the full path to the current PDF
$InputPath = $InputDirectory + '\' + $PDFList[$j]
#Variable to hold output path of each TIF file. Filename format is that used by batches in ScerIS (i.e. 00010001.tif, 00010002.tif, etc...)
$OutputFullDirectory = $inputlDirectory + '\' + $FileName + "_" + "{0:D4}" -f + '1' + '%04d.tif'
#Calls Ghostscript command line executable to process each PDF file into a group of single page TIF image files
&'C:\Program Files\gs\gs9.14\bin\gswin64c.exe' -sDEVICE=tiffg4 -dBATCH -dNOPAUSE -q -r600 "-sOutputFile=$OutputFullDirectory" "$InputPath"
#Increment the counter for the loop
$DocCounter = $j + 1
#Rename the current pdf so that it isn't processed again.
$RenamePath = $PdfList[$j] -replace("pdf", "pd_")
Rename-Item $InputPath $RenamePath -Force
}
}
First : In PowerShell, when you call a function you must not use parenthesis :
convert $hostdirectoryPlusLoanPlusDocType
or as suggested in comment
convert -inputDirectory $hostdirectoryPlusLoanPlusDocType
but not :
convert([string]$hostdirectoryPlusLoanPlusDocType)
Second : Your function should be declarated first and use after :
function toto ($var)
{
Write-Host $var
}
toto "voila"
and not
toto "voila"
function toto ($var)
{
Write-Host $var
}

Powershell with Robocopy and Arguments Passing

I'm trying to write a script that uses robocopy. If I were just doing this manually, my command would be:
robocopy c:\hold\test1 c:\hold\test2 test.txt /NJH /NJS
BUT, when I do this from powershell, like:
$source = "C:\hold\first test"
$destination = "C:\hold\second test"
$robocopyOptions = " /NJH /NJS "
$fileList = "test.txt"
robocopy $source $destination $fileLIst $robocopyOptions
I get:
-------------------------------------------------------------------------------
ROBOCOPY :: Robust File Copy for Windows
-------------------------------------------------------------------------------
Started : Fri Apr 10 09:20:03 2015
Source - C:\hold\first test\
Dest - C:\hold\second test\
Files : test.txt
Options : /COPY:DAT /R:1000000 /W:30
------------------------------------------------------------------------------
ERROR : Invalid Parameter #4 : " /NJH /NJS "
However, if I change the robocopy command to
robocopy $source $destination $fileLIst /NJH /NJS
everything runs successfully.
So, my question is, how can I pass a string as my robocopy command options (and, in a larger sense, do the same for any given external command)
Start robocopy -args "$source $destination $fileLIst $robocopyOptions"
or
robocopy $source $destination $fileLIst $robocopyOptions.split(' ')
Use the arrays, Luke. If you specify an array of values, PowerShell will automatically expand them into separate parameters. In my experience, this is the most reliable method. And it doesn't require you to mess with the Start-Process cmdlet, which is in my opinion is overkill for such tasks.
This trick is from the best article I've seen on the PowerShell behavior towards external executables: PowerShell and external commands done right.
Example:
$source = 'C:\hold\first test'
$destination = 'C:\hold\second test'
$robocopyOptions = #('/NJH', '/NJS')
$fileList = 'test.txt'
$CmdLine = #($source, $destination, $fileList) + $robocopyOptions
& 'robocopy.exe' $CmdLine
You can't use a string to pass options in that way because when you write
robocopy $source $destination $fileList $robocopyOptions
PowerShell will evaluate the last variable ($robocopyOptions) as a single string and it will quote it. This means robocopy will get "/NJH /NHS" (single string, quoted) on its command line. (Obviously not the intent.)
For details on how to work around these kinds of issues, see here:
http://windowsitpro.com/powershell/running-executables-powershell
The article includes the following function:
function Start-Executable {
param(
[String] $FilePath,
[String[]] $ArgumentList
)
$OFS = " "
$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = $FilePath
$process.StartInfo.Arguments = $ArgumentList
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
if ( $process.Start() ) {
$output = $process.StandardOutput.ReadToEnd() `
-replace "\r\n$",""
if ( $output ) {
if ( $output.Contains("`r`n") ) {
$output -split "`r`n"
}
elseif ( $output.Contains("`n") ) {
$output -split "`n"
}
else {
$output
}
}
$process.WaitForExit()
& "$Env:SystemRoot\system32\cmd.exe" `
/c exit $process.ExitCode
}
}
This function will let you run an executable in the current console window and also let you build an array of string parameters to pass to it.
So in your case you could use this function something like this:
Start-Executable robocopy.exe $source,$destination,$fileList,$robocopyOptions
Putting the options in separate arguments worked for me. Using Robocopy for copying excluding any CSV files.
$roboCopyPath = $env:ROBOCOPY_PATH
$otherLogsPath = [System.IO.Path]::Combine($basePath, "Logs-Other")
$atrTestResults = [System.IO.Path]::Combine($Release, $BuildNumber)
$ResultsSummary = [System.IO.Path]::Combine($basePath, "Result")
$robocopyOptions = #("/log:$otherLogsPath\robocopy.log", '/xf', '*.csv')
$CmdLine = #($atrTestResults, $ResultsSummary) + $robocopyOptions
&$roboCopyPath $CmdLine

Timestamp on file name using PowerShell

I have a path in a string,
C:\temp\mybackup.zip
I would like insert a timestamp in that script, for example,
C:\temp\mybackup 2009-12-23.zip
Is there an easy way to do this in PowerShell?
You can insert arbitrary PowerShell script code in a double-quoted string by using a subexpression, for example, $() like so:
"C:\temp\mybackup $(get-date -f yyyy-MM-dd).zip"
And if you are getting the path from somewhere else - already as a string:
$dirName = [io.path]::GetDirectoryName($path)
$filename = [io.path]::GetFileNameWithoutExtension($path)
$ext = [io.path]::GetExtension($path)
$newPath = "$dirName\$filename $(get-date -f yyyy-MM-dd)$ext"
And if the path happens to be coming from the output of Get-ChildItem:
Get-ChildItem *.zip | Foreach {
"$($_.DirectoryName)\$($_.BaseName) $(get-date -f yyyy-MM-dd)$($_.extension)"}
Here's some PowerShell code that should work. You can combine most of this into fewer lines, but I wanted to keep it clear and readable.
[string]$filePath = "C:\tempFile.zip";
[string]$directory = [System.IO.Path]::GetDirectoryName($filePath);
[string]$strippedFileName = [System.IO.Path]::GetFileNameWithoutExtension($filePath);
[string]$extension = [System.IO.Path]::GetExtension($filePath);
[string]$newFileName = $strippedFileName + [DateTime]::Now.ToString("yyyyMMdd-HHmmss") + $extension;
[string]$newFilePath = [System.IO.Path]::Combine($directory, $newFileName);
Move-Item -LiteralPath $filePath -Destination $newFilePath;
I needed to export our security log and wanted the date and time in Coordinated Universal Time. This proved to be a challenge to figure out, but so simple to execute:
wevtutil export-log security c:\users\%username%\SECURITYEVENTLOG-%computername%-$(((get-date).ToUniversalTime()).ToString("yyyyMMddTHHmmssZ")).evtx
The magic code is just this part:
$(((get-date).ToUniversalTime()).ToString("yyyyMMddTHHmmssZ"))
Thanks for the above script. One little modification to add in the file ending correctly. Try this ...
$filenameFormat = "MyFileName" + " " + (Get-Date -Format "yyyy-MM-dd") **+ ".txt"**
Rename-Item -Path "C:\temp\MyFileName.txt" -NewName $filenameFormat
If you have the path on a variable ($pathfile) use this concrete line to get the TimeStamped Path:
(extracted from here: https://powershellexamples.com/home/Article/10/file-management-add-timestamp-to-file-name)
$pathFile = "C:\ProgramData\MyApp\file.txt"
$pathFileTimestamp = [System.IO.Path]::GetDirectoryName($pathFile) + "\" + `
[System.IO.Path]::GetFileNameWithoutExtension($pathFile) + "_" + `
(get-date -format yyyyMMdd_HHmmss) + ([System.IO.Path]::GetExtension($pathFile))
Write-Host "Path+File: $pathFile"
Write-Host "Path+File with Timestamp: $pathFileTimestamp"
Above will return:
PS C:\> Path+File: C:\ProgramData\MyApp\file.txt
Path+File with Timestamp: C:\ProgramData\MyApp\file_20210328_022045.txt
Use:
$filenameFormat = "mybackup.zip" + " " + (Get-Date -Format "yyyy-MM-dd")
Rename-Item -Path "C:\temp\mybackup.zip" -NewName $filenameFormat
Another approach for renaming.
Set-Location C:\Folder_containing_zipfiles
Get-ChildItem -File | ForEach-Object { Rename-Item -Path $_.FullName -NewName
$_.Name.Replace('.zip',"_$(get-date -Format yyyy_MM_dd_hh_mm_ss).zip") }
use variable to rename existing file
Get-Content -Path '${{vars.LOG_PATH}}\eventMapper.log'
$filenameFormat = 'eventMapper-' + (Get-Date -Format 'yyyy-mm-dd-hh-mm') + '.log'
Rename-Item -Path '${{vars.LOG_PATH}}\eventMapper.log' -NewName $filenameFormat
file created --> eventMapper-2023-23-21-10-23.log
Date + Filename - NOT (Filename + Date) - otherwise it messes up file extension.
$filenameFormat = (Get-Date -Format "yyyy-MM-dd") + " " + "mybackup.zip"
Rename-Item -Path "C:\temp\mybackup.zip" -NewName $filenameFormat