Powershell - modify items in ZIP archive - powershell

I read about extracting ZIP archives on Stackoverflow which results in creating following script:
$shell_app=New-Object -com shell.application
Get-ChildItem -name *.zip | ForEach-Object {
$zip_file=$shell_app.NameSpace((Get-Location).path + " \$_")
$destination=$shell_app.NameSpace((Get-Location).path)
$destination.copyhere($zip_file.items()) }
Now I'm interested in manipulating on items in this archive - for example before I unpack all those files, I want to add to their filenames, name of archive.
AS I check, I can get it (with .zip extenston which I want to remove) by simply entering
%zip_file.title
but I'm not aware how to modify file names. Could anybody help or give sufficient resources?

I don't think you can do that with the shell object alone. I think you'll have to extract the file files and then rename using PowerShell. Here's my test code that works. Quick and dirty but it does the trick.
$zip="c:\work\brain.zip"
$shell_app=New-Object -com shell.application
$zip_file=$shell_app.NameSpace($zip)
$destination=$shell_app.NameSpace("G:\test")
$zip_file.items() | foreach {
$newname="{0}_{1}" -f $zip_file.Title,$_.name
Write-Host "Extracting $newname" -ForegroundColor Green
$destination.copyhere($_)
$oldfile=Join-path $destination.Self.Path -ChildPath $_.name
Rename-Item -Path $oldfile -NewName $newname -passthru
}
Why not create a subfolder for each zipfile in the destination?

Related

Merge CSV Files in Powershell traverse subfolders - archiving & deleting old files use folder name for Target-CSV

I want to merge many CSV-files into one (a few hundred files) removing the header row of the added CSVs.
As the files sit in several subfolders I need to start from the root traversing all the subfolders and process all CSVs in there. Before merging I want to archive them with zip deleting old CSVs. The new merged CSV-file and the zip-archive should be named like their parent folder.
In case the Script is started again for the same folder none of already processed files should be damaged or removed accidentally.
I am not a Powershell guy so I have been copying pasting from several resources in the web and came up with the following solution (Sorry don't remember the resources feel free to put references in the comment if you know).
This patch-work code does the job but it doesn't feel very bulletproof. For now it is processing the CSV files in the subfolders only. Processing the files within the given $targDir as well would also be nice.
I am wondering if it could be more compact. Suggestions for improvement are appreciated.
$targDir = "\\Servername\folder\"; #path
Get-ChildItem "$targDir" -Recurse -Directory |
ForEach-Object { #walkinthrough all subfolder-paths
#
Set-Location -Path $_.FullName
#remove existing AllInOne.csv (targed name for a merged file) in case it has been left over from a previous execution.
$FileName = ".\AllInOne.csv"
if (Test-Path $FileName) {
Remove-Item $FileName
}
#remove existing AllInOne.csv (targed name for archived files) in case it has been left over from a previous execution.
$FileName = ".\AllInOne.zip"
if (Test-Path $FileName) {
Remove-Item $FileName
}
#compressing all csv files in the current path, temporarily named AllInOne.zip. Doing that for each file adding it to the archive (with -Update)
# I wonder if there is a more efficient way to do that.
dir $_.FullName | where { $_.Extension -eq ".csv"} | foreach { Compress-Archive $_.FullName -DestinationPath "AllInOne.zip" -Update}
##########################################################
# This code is basically merging all the CSV files
# skipping the header of added files
##########################################################
$getFirstLine = $true
get-childItem ".\*.csv" | foreach {
$filePath = $_
$lines = $lines = Get-Content $filePath
$linesToWrite = switch($getFirstLine) {
$true {$lines}
$false {$lines | Select -Skip 1}
}
$getFirstLine = $false
Add-Content ".\AllInOne.csv" $linesToWrite
# Output file is named AllInOne.csv temporarily - this is not a requirement
# It was simply easier for me to come up with this temp file in the first place (symptomatic for copy&paste).
}
#########################################################
#deleting old csv files
dir $_.FullName | where { $_.Extension -eq ".csv" -and $_ -notlike "AllInOne.csv"} | foreach { Remove-Item $_.FullName}
# Temporarily rename AllinOne files with parent folder name
Get-ChildItem -Path $_.FullName -Filter *.csv | Rename-Item -NewName {$_.Basename.Replace("AllInOne",$_.Directory.Name) + $_.extension}
Get-ChildItem -Path $_.FullName -Filter *.zip | Rename-Item -NewName {$_.Basename.Replace("AllInOne",$_.Directory.Name) + $_.extension}
}
I have been executing it in the Powershell ISE. The Script is for a house keeping only, executed casually and not on a regular base - so performance doesn't matter so much.
I prefer to stick with a script that doesn't depend on additional libraries if possible (e.g. for Zip).
It may not be bulletproof, but I have seen worse cobbled together scripts. It'll definitely do the job you want it to, but here are some small changes that will make it a bit shorter and harder to break.
Since all your files are CSVs and all would have the same headers, you can use Import-CSV to compile all of the files into an array. You won't have to worry about stripping the headers or accidentally removing a row.
Get-ChildItem "*.csv" | Foreach-Object {
$csvArray += Import-CSV $_
}
Then you can just use Export-CSV -Path $_.FullName -NoTypeInformation to output it all in to a new CSV file.
To have it check the root folder and all the subfolders, I would throw all of the lines in the main ForEach loop into a function and then call it once for the root folder and keep the existing loop for all the subfolders.
function CompileCompressCSV {
param (
[string] $Path
)
# Code from inside the ForEach Loop
}
# Main Script
CompileCompressCSV -Path $targetDir
Get-ChildItem -Path $targetDir -Recurse -Directory | ForEach-Object {
CompileCompressCSV -Path $_.FullName
}
This is more of a stylistic choice, but I would do the steps of this script in a slightly different order:
Get Parent Folder Name
Remove old compiled CSVs and ZIPs
Compile CSVs into an array and output with Parent Folder Name
ZIP together CSVs into a file with the Parent Folder Name
Remove all CSV files
Personally, I'd rather name the created files properly the first time instead of having to go back and rename them unless there is absolutely no way around it. That doesn't seem the case for your situation so you should be able to create them with the right name on the first go.

How to rename large number of files using Powershell and a CSV

Ultimately, I need a solid PowerShell script that will take a folder with several hundred video files, import the existing file names into the program, lookup the new file name in a CSV, and rename it. The old filename is simply (ie. File1.mp4, File2.mp4, etc.) I would like to appended a date to the front of the file in the format of (YYYY-MM-DD).
For testing, I created a folder on my desktop with (10) text files, each with a unique file name.
My CSV file appears as follows:
Image of CSV
The "newfilename" column, was created by using the Concatenate command in Excel.
`(=CONCATENATE(TEXT(A2, "yyyy-mm-dd")," ", B2)`
As much as I would just like PowerShell to handle everything, I feel using Excel for most of this might be the best way.
In my testing, everything was in one folder. However, at work, I will have video files on one drive, and the script will have to be in a folder on my desktop. Because I am in a corporate network, I need a special batch file to run my scripts, which is nothing new. I just modify the script name, and away it goes!
So what commands do I need to do in order to have the script separate from the video files AND the CSV file?
Here is the code that I have so far. Everything works when it's in one folder.
PS C:\Users\ceran\Desktop\Rename Project> Import-Csv -Path .\MyFileList.csv | ForEach-Object {
>> $Src = Join-Path -Path $TargetDir -ChildPath $_.filename
>> $Dst = Join-Path -Path $TargetDir -ChildPath $_.newfilename
>> Rename-Item -Path $Src -NewName $Dst
>> }
Thanks in advance for the help!
Chris
I'm not sure what the date column is in your Excel file and if you want to rename all files in the folder, but if that is the case, you don't need a csv file at all and can do this:
$sourceFolder = 'X:\Path\to\the\video\files' # change this to the real path
Get-ChildItem -Path $sourceFolder -Filter '*.mp4' -File | # iterate through the files in the folder
Where-Object {$_.Name -notmatch '^\d{4}-\d{2}-\d{2}'} | # don't rename files that already start with the date
Rename-Item -NewName { '{0:yyyy-MM-dd} {1}' -f $_.LastWriteTime, $_.Name } -WhatIf
This uses parameter -Filter '*.mp4', to get only files with an .mp4 extension. For the files in your testfolder (Desktop\Rename Project), change this to -Filter '*.txt'.
If you want all files renamed, no matter what the extension, simply remove the Filter from the cmdlet.
Because of the -WhatIf switch, no file is actually renamed and the code just shows in the console what would happen. Once satisfied that this is OK, remove the -WhatIf
Hope that helps.
$targetdir="C:\path\to\where\our\file\directory\is"
$pathtocsv="c:\path\to\csv.csv"
Import-Csv -Path $pathtocsv | ForEach-Object {
$Src = Join-Path -Path $TargetDir -ChildPath $_.filename
$Dst = Join-Path -Path $TargetDir -ChildPath $_.newfilename
Rename-Item -Path $Src -NewName $Dst
}
Why would this not work in any situation?
By the way, if the csv had the columns path and newname, it could be piped directly to rename-item:
path,newname
file.txt,file2.txt
import-csv ren.csv | Rename-Item -whatif
What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/file.txt Destination: /Users/js/foo/file2.txt".

PowerShell script isn't copying like I want

Right in the beginning I should note that I am a bloody beginner because I can't attend it classes in my grade.
I want to create a PowerShell script which will copy everything from
C:\Users\Robert\Desktop\test(lots of folders)
to
C:\Users\Robert\Desktop\neu(lots of folders with the exact same names as above)\price
As an absolute beginner I thought that it will be ok to replace the variable folder name with $_Name because it is the same name in both but I am obviously wrong and don't know why.
Here is my attempt
Copy-Item "C:\Users\Robert\Desktop\test\$_name\*" -Destination "C:\Users\Robert\Desktop\neu\$_Name\price" -Recurse
It is copying something but in one package in a new folder in "neu".
I can't avoid creating this script because it would take me at least two or three days to do it by hand.
I am also sorry for my poor English skills
Thank you
the $_ represents the current pipeline item. i don't see a pipeline in there ... [grin]
the following works by grabbing every file in the source dir & its subdirs, and copying that structure to the destination dir. it uses Splatting to structure the parameters neatly.
$SourceDir = "$env:TEMP\Apps - Copy"
$DestDir = "$env:TEMP\Apps - Copy - Two"
$CI_Params = #{
LiteralPath = $SourceDir
Destination = $DestDir
Force = $True
Recurse = $True
}
Copy-Item #CI_Params
If my understanding is correct:
$src = 'C:\Users\Robert\Desktop\test'
$dst = 'C:\Users\Robert\Desktop\neu\{0}\price'
Get-ChildItem $src -Directory | ForEach-Object {
Copy-Item -Path "$($_.FullName)\*" -Destination ($dst -f $_.BaseName) -Recurse -Force -WhatIf
}
Remove -WhatIf to actually do it.

TFS Build and Powershell Post-Build Script

I need to get a TFS build process working where I dont control the agent, or any of the drop locations.
I have a post-build script which is shown Here...but I dont know how to edit it to so that it preserves the folder structure across multiple project directories.
I know I need to edit the file copy itself, but I am unsure of how to go about doing this in powershell...
foreach ($file in $files)
{
#Do something here to create the correct path on the drop server
Copy $file $Env:TF_BUILD_BINARIESDIRECTORY
}
The end goal is that instead of having all the build dlls in a single directory, I have them structured in folders per project.
Following is a simple script to achieve the feature you want, you can update it base on your folder structure and requirement.
Get-ChildItem -Path $Env:TF_BUILD_BUILDDIRECTORY -Filter *.*proj -Recurse | ForEach-Object {
$projectfolder = $_.BaseName
$currentpath = $_.Directory.ToString()
$copypath = $currentpath + '\obj'
$targetpath = $Env:TF_BUILD_BINARIESDIRECTORY + '\' + $projectfolder
Write-Host "Copying files from $copypath to $targetpath"
Copy-Item $copypath $targetpath -Recurse
}

Renaming multiple files in archives

Here's the renaming problem I need to solve:
There are a few hundred .zip archive files which contain at least few files in them.
Files in archives are parts and are named like this:
Data_Customer1_20160101_part1.txt
Data_Customer1_20160101_part2.txt
Data_Customer1_20160101_partN.txt
I need to change Customer1 part to Customer2 using CMD, PowerShell or 7zip.
Approach extract - rename - archive is not acceptable as there are few hundred GBs of data so it would be very slow.
Is there any possible solution to rename files in archives without extrating them?
This can be done using PowerShell and the latest 7-Zip 15.14 exe. Download and install the 7-Zip, once installed 7z.exe should be in the following location C:\Program Files\7-Zip\7z.exe.
$ArchivesFolder = "T:\Your Archives\"
$7zipExe = "C:\Program Files\7-Zip\7z.exe"
$search = "Customer1"
$replace = "Customer2"
gci $ArchivesFolder -Filter "*.zip" | % {
$archive = $_.FullName
Write-Host $archive -ForegroundColor DarkYellow
& $7zipExe l $archive `
| select -Skip 11 `
| %{IF($_.Length -gt 52 ){$_.substring(53)}} `
| ?{$_ -notmatch "\d* files, \d* folders|------------------------" -and $_ -match $search } `
| %{
write-host "Renaming:[$_] To:[$($_ -replace $search,$replace)]" -ForegroundColor cyan
& $7zipExe rn $archive "$_" "$($_ -replace $search,$replace)" | Out-Null # Comment out
}
}
The above PowerShell will recurse through a give folder ($ArchivesFolder) of .zip files. It will look within each zip file and replace/rename any part of the files name that match the give search variable ($search) with the replacement valuve given ($replace).
If you want to see what the script does before running it, comment out the line that has (# Comment out) next to it with a #. This will stop any file from being renamed.
The script is using the rn parameter of 7z.exe The syntax to rename files inside archives is
7z rn <archive_name> <src_file_1> <dest_file_1> [ <src_file_2> <dest_file_2> ... ]
Note This will only work with 15.14 version of 7z.exe.