Powershell - Copying files with space and variables in the path - powershell

I have the script below, I am trying to use copy-item to copy two files. Unfortunately the file path include spaces in them, and I need to use variables to capture the correct files. I have used Test-Path to try and qualify the path, which seem to work, but in the script it is all failures.
$Today = Get-Date -Format "yyyyMMdd"
$Yesterday = (Get-Date).AddDays(-1).ToString('yyyyMMdd')
$folderdate = $Yesterday
$filedate = (Get-Date).AddDays(-1).ToString('dd.MM.yy')
Copy-Item -Path \\posa1251\d$\File_Transfer\NBS\$Today\Daily MI $Yesterday'.xlsx' -Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\
Copy-Item -Path \\posa1251\d$\File_Transfer\NBS\$Yesterday\$filedate' - West Brom MI.xls' -Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\
The file paths without the variables are below:
\\posa1251\d$\File_Transfer\NBS\20230201\Daily MI 20230131.xlsx
\\posa1251\d$\File_Transfer\NBS\20230131\31.01.23 - West Brom MI.xls
\\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\20230131\
Any suggestions would be gratefully received.

Essentially, your paths should be wrapped in a expandable string "..." otherwise each value between the spaces are interpreted and bound to other parameters, considering there are available positional parameters available. See about Parameters for more details.
Take the following simple function to understand what's happening:
function Test {
param(
[Parameter(Mandatory)]
[string[]] $Path,
[Parameter(Mandatory)]
[string] $Destination,
[Parameter(ValueFromRemainingArguments)]
[object[]] $RemainingArgs
)
$PSBoundParameters
}
If we try the paths without quotes:
Test -Path \\posa1251\d$\File_Transfer\NBS\$Today\Daily MI $Yesterday'.xlsx' -Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\
We can see that MI and 20230201.xlsx are being bound to other, remaining in this case, parameters:
Key Value
--- -----
Path {\\posa1251\d$\File_Transfer\NBS\20230202\Daily}
Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\\
RemainingArgs {MI, 20230201.xlsx}
Instead, if we use quotes:
Test -Path "\\posa1251\d$\File_Transfer\NBS\$Today\Daily MI $Yesterday.xlsx" -Destination "\\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\$folderdate\"
There shouldn't be any problem:
Key Value
--- -----
Path {\\posa1251\d$\File_Transfer\NBS\20230202\Daily MI 20230201.xlsx}
Destination \\wbbsmd.co.uk\corpdata\Corp\Group\NBS_WebSave_Reports\\

Related

Using param aliases in PowerShell

I've created a renaming script that includes aliases for variables that I would like to define as parameters when I call the script. If I define the variables in the script the traditional way ($variable = 'defined') the script renames the appropriate file correctly. I think I am misunderstanding the param portion.
Here's the script:
#retrieve the name from the text file
$name = Get-Content "C:\name.txt"
#identify the .jpg that needs to be renamed based on naming (Get-Date -Format "yyyy-MM-dd HH-mm-ss")
$fileName = Get-Date -Format "yyyy-MM-dd"
param (
[Parameter(<#mandatory=$true#>)]
[alias("v")]
[string] $variant,
[Parameter(<#mandatory=$true#>)]
[alias("pc")]
[string] $processClass
)
#identify new name set up and identify the file to address (oldname) then rename it
$newname = "D:\Output\$name" + "." + $variant + ".PC_" + $processClass + ".jpg"
$oldName = gci "D:\Output\" -Filter *.jpg | Where-Object {$_.name -like "*$fileName*"}
rename-item $oldName.FullName $newname -Force
Then I will call the script with the parameters applied like this:
powershell.exe -File C:\Rename.ps1 -v "XXX" -pc "YYY"
However at this point, the $oldName file is only partially renamed, missing the $variant and $processClass portion and I get a PowerShell error:
param : The term 'param' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
It seems im not properly defining the parameters or I have just misunderstood how this part works, I've not tried this before.
Thanks for any suggestions
your problem is the placement of code before the Param() block. [grin] simply move all that pre-Param() code to after the Param() block and things will work.
you may also want to consider adding [CmdletBinding()] just before the Param() to make it a full "advanced function".

Powershell variable contains the value twice

I have a powershell script that is started bij a jenskinsfile. This all works well. But in the script I have a function to download a file. This does not work, because the $filePath variable contains the value twice. When this part would be run the log would look like:
https://example.com/api/download
D:\folder\file_2.txt D:\folder\file_2.txt
How can I get the value only to be there ones in $filePath ?
function DownloadFile ($folder, $version) {
$wc = New-Object System.Net.WebClient
$wc.Headers['Content-Type'] = 'application/json'
$requesturl = "https://example.com/api/download"
$filePath = Join-Path -Path $folder -ChildPath "\file_$version.txt"
Write-Host "$requesturl"
Write-Host "$filePath"
$wc.DownloadFile($requesturl, $filePath)
return $filePath
}
The implication is that the argument you're passing to the -folder parameter (as represented inside your function as the $folder parameter variable) is an array of folder paths, not a single one.
The solution is therefore to make sure that you only pass a single folder path when you call your DownloadFile function; e.g.:
DownloadFile -folder D:\folder -version 2
# With *positional* parameter binding:
DownloadFile D:\folder 2
Since Join-Path accepts an array of paths as a -Path argument, it outputs multiple paths when given an array; e.g.:
PS> $folder = 'c:\abc', 'c:\def'; Join-Path -Path $folder -ChildPath file.txt
c:\abc\file.txt
c:\def\file.txt
Passing an array Write-Host implicitly stringifies it, which means creating a single string composed of the array elements joined with spaces:
PS> $folder = 'c:\abc', 'c:\def'; Write-Host $folder
c:\abc c:\def
(Note that this differs from implicit output / output via Write-Output, which prints each array element on its own line; also, implicit output / Write-Output write to the pipeline, meaning they output data for later processing, whereas Write-Host writes strings to the display).

How to search content in files in Powershell

I want to make a dynamic function that searches for the requested $ErrorCode within the files inputted and eventually copy the files with the error to another folder.
Right now, my code takes only one file and returns the sentence of where the $Error_Code was found. I want to search through multiple files and return the name of the file that have the $ErrorCode.
function SearchError{
Param (
[Parameter (Mandatory=$true)] [STRING] $SourcePath,
[Parameter (Mandatory=$true)] [STRING] $SourceFile,
[Parameter (Mandatory=$true)] [STRING] $ErrorCode,
[Parameter (Mandatory=$true)] [STRING] $FileType
# [Parameter (Mandatory=$true)] [STRING] $DestPath
)
$TargetPath = "$($SourcePath)\$($SourceFile)"
#Return $TargetPath
$DestinationPath = "$($DestPath)"
#Return $DestinationPath
#foreach($error in $TargetPath) {
Get-ChildItem $TargetPath | Select-String -pattern $ErrorCode
}
SearchError
Select-String's output objects - which are of type [Microsoft.PowerShell.Commands.MatchInfo] - have a .Path property that reflects the input file path.
Adding the -List switch to Select-String makes it stop searching after the first match in the file, so you'll get exactly 1 output object for each file in which at least 1 match was found.
Therefore, the following outputs only the paths of the input files in which at least 1 match was found:
Get-ChildItem $TargetPath |
Select-String -List -Pattern $ErrorCode | ForEach-Object Path
Note: -Pattern supports an array of regex patterns, so if you define your $ErrorCode parameter as [string[]], files that have any one of the patterns will match; use -SimpleMatch instead of -Pattern to search by literal substrings instead.
Re:
eventually copy the files with the error to another folder
Simply appending | Copy-Item -Destination $DestPath to the above command should do.
Re:
I want to search through multiple files
Depending on your needs, you can make your $SourcePath and $SourceFile parameters array-valued ([string[]]) and / or pass wildcard expressions as arguments.

Combine multiple log files to archive using PoSh and 7zip

I am trying to join multiple log files into a single archive file, then move that archive file to another location, in an effort to both clean up old log files, and save hard drive space. We have a bunch of tools that all log to the same root, with a per-tool folder for their logs. (E.g.,
C:\ServerLogs
C:\ServerLogs\App1
C:\ServerLogs\2ndApp
each of which will have log files inside, like
C:\ServerLogs\App1\June1.log
C:\ServerLogs\App1\June2.log
C:\ServerLogs\2ndApp\June1.log
C:\ServerLogs\2ndApp\June2.log
I want to go into each of these subfolders, archive up all the files older than 5 days, then move the archive to another (long-term storage) drive and delete the now-zipped files. The tools I'm using are PowerShell and 7zip. The below code is using test locations.
I have cobbled together two scripts from various sources online, over the course of two full shifts, but neither one works right. Here's the first:
# Alias for 7-zip
if (-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) {throw "$env:ProgramFiles\7-Zip\7z.exe needed"}
set-alias 7zip "$env:ProgramFiles\7-Zip\7z.exe"
$Days = 5 #minimum age of files to archive; in other words, newer than this many days ago are ignored
$SourcePath = C:\WorkingFolder\FolderSource\
$DestinationPath = C:\Temp\
$LogsToArchive = Get-ChildItem -Recurse -Path $SourcePath | Where-Object {$_.lastwritetime -le (get-date).addDays(-$Days)}
$archive = $DestinationPath + $now + ".7z"
#endregion
foreach ($log in $LogsToArchive) {
#define Args
$Args = a -mx9 $archive $log
$Command = 7zip
#write-verbose $command
#invoke the command
invoke-expression -command $Command $Args
The problem with this one is that I get errors trying to invoke the expression. I've tried restructuring it, but then I get errors because my $Args have an "a"
So I abandoned this method (despite it being my preferred), and tried this set.
#region Params
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateScript({Test-Path -Path $_ -PathType 'container'})]
[System.String]
$SourceDirectory,
[Parameter(Position=1, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DestinationDirectory
)
#endregion
function Compress-File{
#region Params
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateScript({Test-Path -Path $_ -PathType 'leaf'})]
[System.String]
$InputFile,
[Parameter(Position=1, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String]
$OutputFile
)
#endregion
try{
#Creating buffer with size 50MB
$bytesGZipFileBuffer = New-Object -TypeName byte[](52428800)
$streamGZipFileInput = New-Object -TypeName System.IO.FileStream($InputFile,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read)
$streamGZipFileOutput = New-Object -TypeName System.IO.FileStream($OutputFile,[System.IO.FileMode]::Create,[System.IO.FileAccess]::Write)
$streamGZipFileArchive = New-Object -TypeName System.IO.Compression.GZipStream($streamGZipFileOutput,[System.IO.Compression.CompressionMode]::Compress)
for($iBytes = $streamGZipFileInput.Read($bytesGZipFileBuffer, 0,$bytesGZipFileBuffer.Count);
$iBytes -gt 0;
$iBytes = $streamGZipFileInput.Read($bytesGZipFileBuffer, 0,$bytesGZipFileBuffer.Count)){
$streamGZipFileArchive.Write($bytesGZipFileBuffer,0,$iBytes)
}
$streamGZipFileArchive.Dispose()
$streamGZipFileInput.Close()
$streamGZipFileOutput.Close()
Get-Item $OutputFile
}
catch { throw $_ }
}
Get-ChildItem -Path $SourceDirectory -Recurse -Exclude "*.7z"|ForEach-Object{
if($($_.Attributes -band [System.IO.FileAttributes]::Directory) -ne [System.IO.FileAttributes]::Directory){
#Current file
$curFile = $_
#Check the file wasn't modified recently
if($curFile.LastWriteTime.Date -le (get-date).adddays(-5)){
$containedDir=$curFile.Directory.FullName.Replace($SourceDirectory,$DestinationDirectory)
#if target directory doesn't exist - create
if($(Test-Path -Path "$containedDir") -eq $false){
New-Item -Path "$containedDir" -ItemType directory
}
Write-Host $("Archiving " + $curFile.FullName)
Compress-File -InputFile $curFile.FullName -OutputFile $("$containedDir\" + $curFile.Name + ".7z")
Remove-Item -Path $curFile.FullName
}
}
}
This actually seems to work, insofar as it creates individual archives for each eligible log, but I need to "bundle" up the logs into one mega-archive, and I can't seem to figure out how to recurse (to get sub-level items) and do a foreach (to confirm age) without having that foreach produce individual archives.
I haven't even gotten into the Move and Delete phase, because I can't seem to get the archiving stage to work properly, but I certainly don't mind grinding away at that once this gets figured out (I've already spent two full days trying to figure this one!).
I greatly appreciate any and all suggestions! If I've not explained something, or been a bit unclear, please let me know!
EDIT1: Part of the requirement, which I completely forgot to mention, is that I need to keep the structure in the new location. So the new location will have
C:\ServerLogs --> C:\Archive\
C:\ServerLogs\App1 --> C:\Archive\App1
C:\ServerLogs\2ndApp --> C:\Archive\2ndApp
C:\Archive
C:\Archive\App1\archivedlogs.zip
C:\Archive\2ndApp\archivedlogs.zip
And I have absolutely no idea how to specify that the logs from App1 need to go to App1.
EDIT2: For this latter part, I used Robocopy - It maintains the folder structure, and if you feed it ".zip" as an argument, it'll only do the .zip files.
this line $Args = a -mx9 $archive $log likely needs to have the right side value wrapped in double quotes OR each non-variable wrapped in quotes with a comma between each so that you get an array of args.
another method would be to declare an array of args explicitly. something like this ...
$ArgList = #(
'a'
'-mx9'
$archive
$log
)
i also recommend you NOT use an automatic $Var name. take a look at Get-Help about_Automatic_Variables and you will see that $Args is one of those. you are strongly recommended NOT to use any of them for anything other than reading. writing to them is iffy. [grin]

PowerShell and Robocopy - filename incorrect when trying to pass variable folder as destination

I'm trying to use a PowerShell script running Robocopy* to back some files up to a newly-made directory:
$Timestamp = Get-Date -format ddMMyyyy
$DestFolder = "`"\\NASBOX\Archives\$Timestamp\`""
$SourceFolder = "`"\\DESKTOP\d$`""
ROBOCOPY $SourceFolder $DestFolder /COPYALL /B /R:10 /W:90 /LOG:$Timestamp.txt /FP /TEE
This gives me the following error:
2018/01/23 16:26:20 ERROR 123 (0x0000007B) Accessing Destination Directory \\NASBOX\Archives\23012018" \COPYALL \B \R:10 \W:90 \LOG:23012018.txt \FP \TEE\
The filename, directory name, or volume label syntax is incorrect.
I've tried a few different methods, including passing the arguments as an array. Every single thing I've tried results in the exact same error.
I roughly understand why this is happening, but despite ~two hours spent online I can't find a solution that works in my specific context.
Where am I going wrong?
* I tried using Copy-Item but there are some super long directory paths on this desktop's "D" drive.
The issue is the trailing slash in the path you are building:
"\\NASBOX\Archives\23012018\"
This slash is escaping the double quote for robocopy, it is seeing this path as including a quote symbol at the end :
\\NASBOX\Archives\23012018"
The error message shows this, but isn't very helpful! To fix the issue, simply remove the trailing slash from your path:
$DestFolder = "`"\\NASBOX\Archives\$Timestamp`""
You don't need to try so hard with escaping of quotes in your variables. PowerShell handles most of this for you. This should be all you need to do:
$Timestamp = Get-Date -Format ddMMyyyy
$SourceFolder = "\\DESKTOP\d$"
$DestFolder = "\\NASBOX\Archives\$Timestamp"
ROBOCOPY $SourceFolder $DestFolder /COPYALL /B /R:10 /W:90 /LOG:$Timestamp.txt /FP /TEE
Note that the destination folder shouldn't include a trailing \.
TL;DR - It is not necessary to create strings with embedded " characters to pass to robocopy. Just put the variables on the robocopy command line and PowerShell will quote automatically when necessary.
Function Copy-File {
[CmdletBinding()]
Param(
[Parameter(Position=0)]
[string]$source,
[Parameter(Position=1)]
[string]$dest,
[Parameter(Position=2)]
[string]$sourcefile,
[Parameter(Position=3)]
[ref]$RoboError
)
Write-Log -message "Copying $sourcefile from $source to $dest"
$robotoday=(Get-Date).ToString('yyyyMMdd')
$logfile = -join($env:systemdrive, '\logs\', $robotoday, '_robocopy.log')
$what = #("$sourcefile",'/COPY:DAT', '/Z', '/E')
$options = #("/R:1","/W:1","/TEE","/ETA","/LOG+:$logfile")
$cmdArgs = #($source,$dest,$what,$options)
robocopy #cmdArgs
if ($lastexitcode -gt 7) {
$RoboError.value=$TRUE
Write-Log -level 'warn' -message "Robocopy function failed with error: $lastexitcode"
}
} # End Copy-File
[bool]$RoboError=$FALSE
Copy-File -source $copysource -dest $copydestination -sourcefile '*' -RoboError([ref]$RoboError)