PowerShell extract each zip file to own folder - powershell

I would like to unzip some files each into their own folder with the same name as the zip file. I have been doing clunky things like this, but as this is PowerShell, there is usually a much smarter way to achieve things.
Are there some kind of one-or-two-liner ways that I can operate on each zip file in a folder and extract it into a subfolder of the same name as the zip (but without the extension)?
foreach ($i in $zipfiles) {
$src = $i.FullName
$name = $i.Name
$ext = $i.Extension
$name_noext = ($name -split $ext)[0]
$out = Split-Path $src
$dst = Join-Path $out $name_noext
$info += "`n`n$name`n==========`n"
if (!(Test-Path $dst)) {
New-Item -Type Directory $dst -EA Silent | Out-Null
Expand-Archive -LiteralPath $src -DestinationPath $dst -EA Silent | Out-Null
}
}

You could do with a few less variables. When the $zipfiles collection contains FileInfo objects as it appears, most variables can be replaced by using the properties the objects already have.
Also, try to avoid concatenating to a variable with += because that is both time and memory consuming.
Just capture the result of whatever you output in the loop in a variable.
Something like this:
# capture the stuff you want here as array
$info = foreach ($zip in $zipfiles) {
# output whatever you need to be collected in $info
$zip.Name
# construct the folderpath for the unzipped files
$dst = Join-Path -Path $zip.DirectoryName -ChildPath $zip.BaseName
if (!(Test-Path $dst -PathType Container)) {
$null = New-Item -ItemType Directory $dst -ErrorAction SilentlyContinue
$null = Expand-Archive -LiteralPath $zip.FullName -DestinationPath $dst -ErrorAction SilentlyContinue
}
}
# now you can create a multiline string from the $info array
$result = $info -join "`r`n==========`r`n"

Related

How to use a condition when writing to a .txt file

I am trying to create myself a powershell script that has mainly two tasks - one after the other.
Initial assumptions: it runs where it performs the following tasks.
Trace all the files, those that are folders - make a zip of them, then after making the archive, delete them. Objective accomplished.
Write to the file the names of all files in the folder (after completing point 1) but check if the given file has the extension of *.zip
a) if it has extension of *.zip then it should be saved in .txt file like "uresoruce = foo/file.zip"
b) if it doesn't have extension *.zip then it should be saved to a .txt file like this "resoruce = foo/file2.jar"
c) since the script is started from the place where all the files are, it will probably also be saved to a file, I would like to avoid this
Suppose we have some files in a folder after compiling, and we don't have any folder inside. The .txt file should look like the following:
uresource = plugins/Liula_1.0.0.0.zip
uresource = plugins/Liborts_3.7.1.0.zip
uresource = plugins/Liwer_1.2.0.0.zip
resource = plugins/o0.I20100512-1500.jar
resource = plugins/or.v20100505-1235.jar
The script I managed to write so far:
## set current path
$path = (Resolve-Path .\).Path
## dirs in a path
$source = Get-ChildItem -Path $path -Filter "" -Directory
$files = Get-ChildItem $path
Add-Type -assembly "system.io.compression.filesystem"
Foreach ($s in $source) {
$destination = Join-path -path $path -ChildPath "$($s.name).zip"
[io.compression.zipfile]::CreateFromDirectory($s.fullname, $destination)
Remove-Item $s -Recurse
}
# This does not working!!! :/
Foreach ($f in $files) {
$extn = [IO.Path]::GetExtension($f)
if ($extn -eq ".zip" ) {
$outfile = "uresource = plugins/" + $f.Name
}
else {
$outfile = "resource = plugins/" + $f.Name
}
}
As #Santiago Squarzon rightly pointed out I did not do anything with this variable. I got a little confused because before I did it only for out-file I didn't use -append and in fact I got the last value in .txt. Now I made my first script in ph, it works like a dream ;)
## set current path
$path = (Resolve-Path .\).Path
## dirs in a path
$source = Get-ChildItem -Path $path -Filter "" -Directory
$files = Get-ChildItem $path
Add-Type -assembly "system.io.compression.filesystem"
## create zip and delete other dirs
Foreach ($s in $source) {
$destination = Join-path -path $path -ChildPath "$($s.name).zip"
[io.compression.zipfile]::CreateFromDirectory($s.fullname, $destination)
Remove-Item $s -Recurse
}
## output filename in .txt
$logFile = "$pwd\logfile.txt"
Foreach ($f in $files) {
$extn = [IO.Path]::GetExtension($f)
if ($extn -eq ".zip" ) {$outfile = "uresource = plugins/$f"}
else {$outfile = "resource = plugins/$f"}
$outfile | Out-File -Append $logFile
}

Powershell script to create a single folder based of the 5 digits in the name of the pdf's

So far I have tried the following script:
$SourceFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\"
$TargetFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\Final\"
Get-ChildItem -Path $SourceFolder -Filter *.pdf |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.pdf','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
if( -not ( Test-Path -Path $Destination.Directory.FullName )){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
This creates a folder for every pdf in the folder.
I need it create a single folder based on the 5 digit in the name and move those files into the new folder.
For example: I could have 10 pdf's that have the number "30565" in them and the new folder should be named "30565"
Here are some file names to explain:
LKY_20974_Pr01_1-5000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr02_5001-10000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr03_10001-15000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
I have tried to include an else block to the best answer script and haven't had much success. I did however create a separate script that will archive the files before creating a new file. I just have to run it before the main powershell script.
$SourceDir = 'D:\WORK\JetLetter\LKY\LKY_jV_004_9835'
$DestDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files'
$ArchiveDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files\#archive'
$Filter = '*.pdf'
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (Test-Path -LiteralPath $FullTargetDir)
{
# the "$Null =" is to suppress unwanted output about what was done
$null = Move-Item -Path $FullTargetDir -Destination $ArchiveDir -Force
}
}
This has made the files and folders a lot more organized.
i think this does what you want done. [grin] the comments seem adequate, but if you have any questions, please ask.
$SourceDir = 'c:\temp\JetLetter\LKY\LKY_jv_004'
$DestDir = 'c:\temp\JetLetter\LKY\LKY_jv_004\Final'
$Filter = '*.pdf'
#region >>> make the dirs and sample files to work with
# remove the entire "#region/#endregion" block when you are ready to work with real data
# make the dirs
$Null = mkdir -Path $SourceDir, $DestDir -ErrorAction 'SilentlyContinue'
# make the test files
$SampleFiles = #(
'LKY_11111_Pr11_1-11111.pdf'
'LKY_22222_Pr22_2-22222.pdf'
'LKY_22222_Pr22_2222-2222.pdf'
'LKY_33333_Pr33_3-3333.pdf'
'LKY_33333_Pr33_33333-33333.pdf'
'LKY_55555_Pr55_5-5555.pdf'
'LKY_77777_Pr77_7-77777.pdf'
'LKY_77777_Pr77_77777-77777.pdf'
'LKY_99999_Pr99_9-99999.pdf'
)
foreach ($SF_Item in $SampleFiles)
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $SourceDir -Name $SF_Item -ItemType 'File' -ErrorAction 'SilentlyContinue'
}
#endregion >>> make the dirs and sample files to work with
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (-not (Test-Path -LiteralPath $FullTargetDir))
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $FullTargetDir -ItemType 'Directory'
}
$NewFullFileName = Join-Path -Path $FullTargetDir -ChildPath $FL_Item.Name
# leave the file in the source dir if it already is in the final target dir
# you may want to save the not-copied info to a file for later review
if (-not (Test-Path -LiteralPath $NewFullFileName))
{
# the "Move-Item" cmdlet on win7ps5.1 is wildly unreliable
# so i used copy & then remove
$Null = Copy-Item -LiteralPath $FL_Item.FullName -Destination $NewFullFileName
Remove-Item -LiteralPath $FL_Item.FullName
}
else
{
Write-Warning (' {0} already exists in {1}' -f $FL_Item.Name, $FullTargetDir)
Write-Warning ' The file was not moved.'
Write-Warning ''
}
}
screen output only exists for "not moved" files. again, you may want to save the list to a $Var or to a file for later work.
one of the moved files ...
C:\Temp\JetLetter\LKY\LKY_jv_004\Final\22222\LKY_22222_Pr22_2222-2222.pdf

PowerShell read csv file and create files accordingly

Basically, I have a CSV file with a lot of columns. Let's say it looks like this:
_username, platform_
username1, platformX1
username2, platformY
username3, platformX2
username4, platformX2
..., ...
I would like to write a script that goes through the file and for each platform, it creates a file with the specific username in a different folder, so I would have:
\platformX1\username1.file
\platformY\username2.file
\platformX2\username3.file, username4.file
etc etc...
I know I should use foreach with an if placed somewhere, but powershell is new for me, and I don't really know how to start it.
Here's something similar tweaked to what you want to do.
This created files of a specific size in your directory
$data = get-content "C:\Data\masteFile.csv"
$drcty = "C:\Data"
foreach($line in $data)
{
$a,$b = $line.Split("{,}")
$parent = $drcty+"\"+$a+"\"
$filename = $parent + $b.TRIM() +".txt"
write-host $filename
New-Item -ItemType directory -Path $parent
fsutil file createnew $filename 2000
}
this seems to do what you want. [grin]
# fake reading in a CSV file
# in real life, use Import-CSV
$InStuff = #'
_UserName, Platform_
username1, platformX1
username2, platformY
username3, platformX2
username4, platformX2
'# | ConvertFrom-Csv
$DestDir = $env:TEMP
$Extension = 'txt'
foreach ($IS_Item in $InStuff)
{
$TargetPath = Join-Path -Path $DestDir -ChildPath $IS_Item.Platform_
if (-not (Test-Path -LiteralPath $TargetPath))
{
# "$Null = " will supress unwanted output from New-Item
$Null = New-Item -Path $TargetPath -ItemType Directory
}
$FileName = $IS_Item._UserName, $Extension -join '.'
$FullFileName = Join-Path -Path $TargetPath -ChildPath $FileName
if (-not (Test-Path -LiteralPath $FullFileName))
{
# "$Null = " will supress unwanted output from New-Item
$Null = New-Item -Path $FullFileName -ItemType File
}
}
i think what it does is apparent, but i know i get ahead of where i otta be at times. so, if there are any questions, please feel free to ask ... [grin]
This worked for me.
$ErrorActionPreference = "Stop"
#Path of the CSV file resides
$csvData = Import-Csv -Path "$env:USERPROFILE\Desktop\data.csv"
$DestinationFolder = "C:\Stuffs"
foreach($record in $csvData)
{
$destPlatformFolder = $DestinationFolder + "\" + $record.platform_
if(-not(Test-Path -Path $destPlatformFolder)){
New-Item -Path $destPlatformFolder -ItemType Directory -Force | Out-Null
}
$destinationFile = $destPlatformFolder + "\" + $record._username
New-Item -Path $destinationFile -ItemType File -Force | Out-Null
}

Compress Files from list with PowerShell 5

I have a file with a list of files with directions.
Like this:
XXXXXX/sample.txt
XXXXXX/dog.txt
XXXXXX/cat.docx
ZZZZ/lamp.jpg
How can I to compress all files and save files with sub-directions.
Now, I can to compress all files but without directions.
Like this:
sample.txt
dog.txt
cat.docx
lamp.jpg
Sorry on my bad English.
foreach ($filename in Get-Content .\ListOfFilesToZip.txt)
{
Compress-Archive -Update $filename .\ZipFile.zip
}
The following function can compresses a whole folder structure into a single zip file and will keep the structure within the zip (even works with PowerShell <5).
function createZipFile($outputFileName, $sourceDirectory){
Add-Type -AssemblyName System.IO.Compression.FileSystem
$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
[System.IO.Compression.ZipFile]::CreateFromDirectory($sourceDirectory, $outputFileName, $compressionLevel, $false)
}
Call it like that:
createZipFile "c:\temp\output.zip" "c:\folder\to\compress"
Not sure if there's any better way of doing this, but you can create a temp folder, copy the files to be archived along with desired folder structure over there and then just zip up the whole thing.
Take a look here (not my code, but seems to do exactly that) for a sample
Edit If question is about getting filename from the path
The most efficient way would be with a regex, but they can be rather complicated if you are not used to working with them.
A simpler way would be to simply split each string and select the last part where the file name is:
$string = "c:\asd\qwe\zxc\dog.txt"
#Split the string on each \
$string.Split("\")
#This will output a list like this
c:
asd
qwe
zxc
dog.txt
Now we want to simply select the last entry in this list as the filename is always last in a path. So we use Select Object for that.
$string.Split("\") | Select-Object -Last 1
This will return:
dog.txt
You can run this through a foreach on your list to get it for each item.
function DirNewTemp {
$tmpdir = (New-TemporaryFile).FullName
Remove-Item -Force -Path $tmpdir -ErrorAction 'SilentlyContinue'
New-Item -Type Directory $tmpdir | out-null
return $tmpdir
}
function ZipFiles {
param(
[string]$Zip,
[string[]]$FileList
)
write-host ""
write-host "====================================="
write-host "ZipFile $Zip"
write-host "====================================="
$pwd = (Get-Location).Path
$tmpdir = DirNewTemp
try {
$count = $FileList.Count
foreach ($file in $FileList) {
$pwd_escaped = [Regex]::Escape($pwd)
$save_file = $file -replace "^${pwd_escaped}\\", "${tmpdir}\"
$save_path = Split-Path $save_file -Parent
New-Item -Force -Path $save_path -ItemType Directory | out-null
Copy-Item -Force $file $save_file
}
set-location $tmpdir
$dest = "$pwd\$Zip";
write-host "Creating Zip: $dest"
Compress-Archive -Force -Path * -DestinationPath $dest
}
finally {
write-host "cleanup"
set-location $pwd
remove-item -Force -Recurse -Path $tmpdir -ErrorAction 'SilentlyContinue'
}
}

Most elegant way to extract a directory from a zipfile using PowerShell?

I need to unzip a specific directory from a zipfile.
Like for example extract the directory 'test\etc\script' from zipfile 'c:\tmp\test.zip' and place it in c:\tmp\output\test\etc\script.
The code below works but has two quirks:
I need to recursively find the directory ('script') in the zip file (function finditem) although I already know the path ('c:\tmp\test.zip\test\etc\script')
With CopyHere I need to determine the targetdirectory, specifically the 'test\etc' part manually
Any better solutions? Thanks.
The code:
function finditem($items, $itemname)
{
foreach($item In $items)
{
if ($item.GetFolder -ne $Null)
{
finditem $item.GetFolder.items() $itemname
}
if ($item.name -Like $itemname)
{
return $item
}
}
}
$source = 'c:\tmp\test.zip'
$target = 'c:\tmp\output'
$shell = new-object -com shell.application
# find script folder e.g. c:\tmp\test.zip\test\etc\script
$item = finditem $shell.NameSpace($source).Items() "script"
# output folder is c:\tmp\output\test\etc
$targetfolder = Join-Path $target ((split-path $item.path -Parent) -replace '^.*zip')
New-Item $targetfolder -ItemType directory -ErrorAction Ignore
# unzip c:\tmp\test.zip\test\etc\script to c:\tmp\output\test\etc
$shell.NameSpace($targetfolder).CopyHere($item)
I don't know about most elegant, but with .Net 4.5 installed you could use the ZipFile class from the System.IO.Compression namespace:
[Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null
$zipfile = 'C:\path\to\your.zip'
$folder = 'folder\inside\zipfile'
$dst = 'C:\output\folder'
[IO.Compression.ZipFile]::OpenRead($zipfile).Entries | ? {
$_.FullName -like "$($folder -replace '\\','/')/*"
} | % {
$file = Join-Path $dst $_.FullName
$parent = Split-Path -Parent $file
if (-not (Test-Path -LiteralPath $parent)) {
New-Item -Path $parent -Type Directory | Out-Null
}
[IO.Compression.ZipFileExtensions]::ExtractToFile($_, $file, $true)
}
The 3rd parameter of ExtractToFile() can be omitted. If present it defines whether existing files will be overwritten or not.
As far as the folder location in a zip is known, the original code can be simplified:
$source = 'c:\tmp\test.zip' # zip file
$target = 'c:\tmp\output' # target root
$folder = 'test\etc\script' # path in the zip
$shell = New-Object -ComObject Shell.Application
# find script folder e.g. c:\tmp\test.zip\test\etc\script
$item = $shell.NameSpace("$source\$folder")
# actual destination directory
$path = Split-Path (Join-Path $target $folder)
if (!(Test-Path $path)) {$null = mkdir $path}
# unzip c:\tmp\test.zip\test\etc\script to c:\tmp\output\test\etc\script
$shell.NameSpace($path).CopyHere($item)
Windows PowerShell 5.0 (included in Windows 10) natively supports extracting ZIP files using Expand-Archive cmdlet:
Expand-Archive -Path Draft.Zip -DestinationPath C:\Reference