How to compress multiple files into one zip with PowerShell? - powershell

I want to compress multiple files into one zip.
I am stuck with this at the moment:
Get-ChildItem -path C:\logs -Recurse | Where {$_.Extension -eq ".csv" -and $_.LastWriteTime -lt (Get-Date).AddDays(-7)} | write-zip -level 9 -append ($_.LastWriteTime).zip | move-item -Force -Destination {
$dir = "C:\backup\archive"
$null = mkdir $dir -Force
"$dir\"
}
I get this exception
Write-Zip : Cannot bind argument to parameter 'Path' because it is null.
This part is the problem:
write-zip -level 9 -append ($_.LastWriteTime).zip
I have never used powershell before but i have to provide a script, I can't provide a c# solution.

The problem is that Get-ChildItem returns instances of the System.IO.FileInfo class, which doesn't have a property named Path. Therefore the value cannot be automatically mapped to the Path parameter of the Write-Zip cmdlet through piping.
You'll have to use the ForEach-Object cmdlet to zip the files using the System.IO.FileInfo.FullName property, which contains the full path:
Get-ChildItem -Path C:\Logs | Where-Object { $_.Extension -eq ".txt" } | ForEach-Object { Write-Zip -Path $_.FullName -OutputPath "$_.zip" }
Here's a shorter version of the command using aliases and positional parameters:
dir C:\Logs | where { $_.Extension -eq ".txt" } | foreach { Write-Zip $_.FullName "$_.zip" }
Related resources:
Cool Pipeline Tricks, Redux (TechNet Magazine)

Related

Move-Item doesn't move file

I've tried to rename a csv with powershell and then move it automatically to another folder, when there's no file.
Originally, the csv-name looks like this: import_9999_2020-08-13_132238.csv but the part with 9999 can also include just 2 or 3 digits.
My actual Code looks like:
#Import of path and target-path
$path = "\\network-path\subfolder\subfolder\subfolder\subfolder\subfolder1\"
$target_path = "\\network-path\subfolder\subfolder\subfolder\subfolder\subfolder2\"
#endless loop
$a=$true
while($a -eq $true){
$Files = gci $path
$TargetFiles = gci $target_path
#wait 5 minutes if path is empty
if(($Files).Count -eq 0){
sleep -Seconds 300
}
#if path is filled with one or more files
else {
#if file in target-path is processed (from another program)
if(($TargetFiles).count -eq 0){
#rename and move the latest file
get-childitem -path $path -Filter "import_*.csv"|
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 |
Rename-Item -NewName {($_.Name.Substring(0,($_.Name.Length)-22))+".csv"} |
Move-Item -Destination $target_path +"$($_.Name).csv"
}
sleep -Seconds 20
}
}
It works partly and renames the csv, but it doesn't move it to the target-path. The path is correct, i've copied it from the Windows-Explorer.
Any Ideas, why the program doesn't work completely? Thanks
For better readability, I would split the code where you find the original file, rename it and move it to the target path into several lines.
Also, Move-Item can rename the file aswell, so no need to do a Rename-Item first:
$file = Get-ChildItem -Path $path -Filter "import_*.csv" -File |
Sort-Object -Property $_.LastWriteTime |
Select-Object -Last 1
# create the new name for the file.
# or use regex: $newName = '{0}{1}' -f ($file.BaseName -replace '(_\d{4}-\d{2}-\d{2}_\d+)$'), $file.Extension
$newName = '{0}{1}' -f (($file.BaseName -split '_')[0..1] -join '_'), $file.Extension
# now move the file and rename at the same time
$file | Move-Item -Destination (Join-Path -path $target_path -ChildPath $newName)
I changed CreationTime to LastWriteTime to really get the latest file.
I also added the -File switch to the Get-ChildItem cmdlet, because only for old PowerShell versions you need to use where-object { -not $_.PSIsContainer }
If you want to pass it down the pipeline add -Passthru to the Rename-Item cmdlet
Somefile.txt | Rename-Item -NewName {$_.basename + "abc" + $_.ext} | # nothing in the pipeline
Somefile.txt | Rename-Item -NewName {$_.basename + "abc" + $_.ext} -Passthru | # now the new named fileinfo object is in the pipeline, contained in automatic variable $_

Copy file with certain name into a new folder

I have a powershell script that will search through the sub folders of a directory and copy any files that contain a specific string in the name and then move those into a different folder that it creates. The problem I am having is that the script is not creating and new folder but just a new file without an extension. Here is my script.
Get-ChildItem -Path 'C:\users\user1\Documents\q3\' -Recurse | Where-Object { $_.Name -like "*test2*" -and $_.FullName -notmatch 'newfolder' } | Copy-Item -Destination 'C:\Users\user1\Documents\Q3\test'
There is no parameter with a behavior like: "createifnotexist" for directories in powershell's copy-item() function. (I'm using powershell 5.1 and could not find one)
This is a way to achieve your goal with a one liner
New-Item -ItemType Directory -Name "test" -Path 'C:\Users\user1\Documents\Q3' -Force; Get-ChildItem -Path 'C:\users\user1\Documents\q3\' -Recurse | Where-Object { $_.Name -like "*test2*" -and $_.FullName -notmatch 'newfolder' } | % { Copy-Item -Path "C:\Users\user1\Documents\Q3\test\"}
The New-Item will override the latest created folder with the -Force

Powershell -renaming a file after copying

I'm having ongoing trouble with a script I've written that is (supposed) to do the following.
I have one folder with a number of csv files, and I want to copy the latest file with the company name into another folder, and rename it.
It is in the current format:
21Feb17070051_CompanyName_Sent21022017
I want it in the following format:
CompanyName21022017
So I have the following powershell script to do this:
## Declare variables ##
$DateStamp = get-date -uformat "%Y%m%d"
$csv_dest = "C:\Dest"
$csv_path = "C:\Location"
## Copy latest Company CSV file ##
get-childitem -path $csv_path -Filter "*Company*.csv" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 |
copy-item -Destination $csv_dest
## Rename the file that has been moved ##
get-childitem -path $csv_dest -Filter "*Company*.csv" |
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 | rename-item $file -NewName {"Company" + $DateStamp + ".csv"}
The file seems to copy ok, but the rename fails -
Rename-Item : Cannot bind argument to parameter 'Path' because it is null.
At C:\Powershell Scripts\MoveCompanyFiles.ps1:20 char:41
+ select-object -last 1 | rename-item $file -NewName {"CompanyName" + $DateSt ...
I think it is something to do with the order in which powershell works, or the fact it can't see the .csv in the $file variable. There are other files (text files, batch files) in the destination, in case that affects things.
Any help in where I'm going wrong would be appreciated.
As wOxxOm answered, you need to remove $file from Rename-Item as it is not defined and the cmdlet already receives the inputobject through the pipeline.
I would also suggest that you combine the two operations by passing through the fileinfo-object for the copied file to Rename-Item. Ex:
## Declare variables ##
$DateStamp = get-date -uformat "%Y%m%d"
$csv_dest = "C:\Dest"
$csv_path = "C:\Location"
## Copy and rename latest Company CSV file ##
Get-ChildItem -Path $csv_path -Filter "*Company*.csv" |
Where-Object { -not $_.PSIsContainer } |
Sort-Object -Property CreationTime |
Select-Object -Last 1 |
Copy-Item -Destination $csv_dest -PassThru |
Rename-Item -NewName {"Company" + $DateStamp + ".csv"}
You can rename and copy in a single command. Just use Copy-Item Command and give new path and name as -Destination parameter value. It will copy and rename the file. You can find an example below.
$source_path = "c:\devops\test"
$destination_path = "c:\devops\test\"
$file_name_pattern = "*.nupkg"
get-childitem -path $source_path -Filter $file_name_pattern |
Copy-Item -Destination { $destination_path + $_.Name.Split("-")[0] + ".nupkg"}

How to add print to console in PowerShell script?

I am new to PowerShell and I have created the following code to delete specific files and folders:
$myFolderPath = "C:\Test"
$myLimit = (Get-Date).AddDays(-14)
# Delete files according to filter.
Get-ChildItem -Path $myFolderPath -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $myLimit} | Remove-Item -Force
# Delete empty folders.
Get-ChildItem -Path $myFolderPath -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
Is it possible to print out the full path of each item that will be removed to the console before the actual Remove-Item operation will be performed?
I guess sth. has to be added here....
... | Remove-Item -Force
and here...
... | Remove-Item -Force -Recurse
but I cannot find out how to implement that in an elegant way (without code duplication).
You can replace the remove-Item-Parts with
Foreach-Object { $_.fullname; Remove-Item -Path $_.Fullname (-Recurse) -Force}
LotPings comment might be better idea, if that is what you want.
It does not get a lot of attention but Tee-Object could be a simple addition to the pipeline here. Redirect the output to a variable that you can print later.
...Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $myLimit} |
Tee-Object -Variable removed | Remove-Item -Force
$removed | Write-Host
All of the file objects piped will be sent to $removed and then to Remove-Item. Since you have more than one delete pipeline you can also use the -Append parameter so that all files are saved in one variable if you so desired.
However this does not mean they were deleted. Just they made it passed the pipe. If you really wanted to be sure you should be using another utility like robocopy which has logging features.

Powershell compare-object

I trying to make a script which compare two directory ( source, destination) and if there are a difference on destination, copy files from source to destination.
The problem is that I don't know how copy the tree of files too.
Example:
$s = "C:\source\client"
$t = "C:\destination\client"
$target = Get-ChildItem $t -Recurse
$source = get-childitem $s -Recurse
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } |
foreach-object -process{
copy-item $_.FullName -destination $t
}
If I have a file in source ( C:\source\client\bin\file.txt) and not in the destination folder, how is the code to copy the file in C:\destination\client\bin\file.txt ?
Thanks.
I am in the process of testing this more. From what i can see the logic of your code is sound.
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } | Select-Object -ExpandProperty inputobject |
foreach-object -process{
copy-item $_.FullName -destination $t
}
Once you have the compare done pipe the results after the Where in Select-Object -ExpandProperty inputobject to extract the File item so that you can see the FullName property
copy-item has a -recurse parameter that will let you specify the root of a directory and then copy everything below it
copy-item c:\test d:\test -recurse -force
Edit:
The problem is for repeated tasks you can't stop it from trying to overwrite everything. You can add -force to make it do it, but it is not very efficient.
Alternatively (and probably a better and simpler way to go about this) you could call robocopy with the /mir switch
Thanks for sharing. Here is what I have done with everything I searched to compare MD5 and then copy only newly added and different files.
With [Compare contents of two folders using PowerShell Get-FileHash] from http://almoselhy.azurewebsites.net/2014/12/compare-contents-of-two-folders-using-powershell-get-filehash/
$LeftFolder = "D:\YFMS_Target"
$RightFolder = "D:\YFMS_Copy"
$LeftSideHash = #(Get-ChildItem $LeftFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($LeftFolder,"")}},Hash)
$RightSideHash = #(Get-ChildItem $RightFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($RightFolder,"")}},Hash)
robocopy $LeftFolder $RightFolder /e /xf *
Write-Host "robocopy LastExitCode: $LastExitCode"
if ($LastExitCode -gt 7) { exit $LastExitCode } else { $global:LastExitCode = $null }
Compare-Object $LeftSideHash $RightSideHash -Property Path,Hash | Where-Object { $_.SideIndicator -eq '<=' } | foreach { Copy-Item -LiteralPath (Join-Path $LeftFolder $_.Path) -Destination (Join-Path $RightFolder $_.Path) -verbose}