Compress Files from list with PowerShell 5 - powershell

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'
}
}

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 extract each zip file to own folder

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"

How to backup these files into specific folders using powershell

I've finally have given up googling and come here out of desperation. Go easy on me I'm fairly new to Powershell.
So, the objective of the code below was to first look through the source folder, then read through each .zip file and move to the directory specified by the value in the hashtable. Unfortunately, this is not how they want it to work anymore.
Now I need to retain the parent folder from source: for example "DAL" and then create the proceeding folders based on the file names and finally move each .zip to its file specified folder. Also, it needs to go through each folder under source which will be at least 20 other folders with a unique 3 character names.
$srcRoot = "C:\Cloud\source\dal"
$dstRoot = "C:\Cloud\Destination"
##$map = #{}; dir -recurse | ? { !$_.psiscontainer} | % { ##$map.add($_.name,$_.PSChildName) }
# DAT and DEV will have to be excluded from folder creation
$map = {
#AEODDAT_201901 = "AEOD\2019\01"
#AEOMDEV_201902 = "AEOM\2019\01"
#AEOYDAT_201902 = "AEOY\2019\01"
}
$fileList = Get-ChildItem -Path $srcRoot -Filter "*.zip*" -File -Force -Recurse
foreach ($file in $fileList)
{
#Go through each file up to mapped string
$key = $file.BaseName.Substring(0,14)
if ($key -in $map.Keys)
{
$fileName = $file.Name
$dstDir = Join-Path -Path $dstRoot -ChildPath $map[$key]
#create direcotory if not in path
if (-not (Test-Path -Path $dstDir))
{
mkdir -Path $dstDir
}
Write-Verbose "Moving $($file.FullName)"
if (Test-Path -Path (Join-Path -Path $dstDir -ChildPath $fileName))
{
#Write error if name exists
Write-Error -Message "File $fileName already exists at $dstDir"
#move path
} else {
Move-Item -Path $($file.FullName) -Destination $dstDir
}
}
}
So C:\Cloud\source\DAL\AEODDAT20190101.zip should create folders in C:\Cloud\Destination\DAL\AEOD\2019\01\AEODDAT20190101.zip would be my desired output.
Welcome, Matt! (no pun intended) One of the habits I have in similar situations with destination folders is to Set-Location $dstRoot and create folders from the relative path. You can execute New-Item with the relative path and the syntax is simpler. For example, your If statement could look like this and it would work the same way (with a slightly different error message):
if ($key -in $map.Keys){
Set-Location $dstRoot
New-Item -ItemType Directory $map[$key] -ErrorAction Ignore #won't raise an error if it exists
Write-Verbose "Moving $($file.FullName)"
#this will raise an error if the file already exists, unless you specify -Force
Move-Item "$($file.FullName)" $map[$key]
}
EDIT: Found 2 issues.
$map is a Hashtable literal that should be preceded with #:
$map = #{
AEODDAT20190101 = "AEOD\2019\01"
You were missing the last character of the base file name by taking only the first 14 characters. AEODDAT2019010 didn't match AEODDAT20190101. This should fix it:
$key = $file.BaseName.Substring(0,15)

Powershell: You cannot call a null-valued expression

Hello Stack Overflow Community,
at the moment I'm struggling with this code (it's not that beautiful):
$filepath = "C:\inetpub\logs\LogFiles"
$filearchivepath = "C:\inetpub\logs"
$daystoarchive = 1
$_ = "";
function create-7zip([String] $aDirectory, [String] $aZipfile){
#change the path where you downloaded the 7z exe
[string]$pathToZipExe = "C:\Users\kschweiger\Downloads\7za.exe";
[Array]$arguments = "a", "-tzip", "$aZipfile", "$aDirectory";
& $pathToZipExe $arguments;
}
#Create a new folder with the specific date
$ArchiveFolder = (Get-Date -Format dd.MM.yyyy) + " - Logs-Archive"
if(Test-Path "$filearchivepath\$ArchiveFolder"){
Write-Host "Folder already exists!"
}else{
New-Item -Path $filearchivepath -Name $ArchiveFolder -ItemType directory
}
#Save alle files older than X days into $Files
$Files = Get-ChildItem -Path $filepath -Recurse | where {$_.LastWriteTime -lt (Get-Date).AddDays(-$daystoarchive)}
#Copy/Move files and keep folder structure
foreach ($File in $Files){
$NewPath = $File.DirectoryName.Replace($filepath,"")
if (!(Test-Path "$filearchivepath\$ArchiveFolder\$NewPath"))
{
New-Item -Path "$filearchivepath\$ArchiveFolder\$NewPath" -ItemType Directory
}
$File | Copy-Item -Destination "$filearchivepath\$ArchiveFolder\$NewPath"
}
#Compress folder
if(Test-Path "$filearchivepath\$ArchiveFolder.zip"){
Write-Host "Archive-File already exists!"
}else{
#[IO.Compression.ZipFile]::CreateFromDirectory("$filearchivepath\$ArchiveFolder","$filearchivepath\$ArchiveFolder.zip")
create-7zip "$filearchivepath\$ArchiveFolder" "$filearchivepath\$ArchiveFolder.zip"
#Delete Folder
Remove-Item -Path "$filearchivepath\$ArchiveFolder" -Recurse -Force
}
The code works. but I also get a error message called:
You cannot call a null-valued expression
How can I resolve this?
Get-ChildItem by default returns files and folders. If you need only files, you should use -File. Otherwise, your $Files will contain folders too (as they have LastWriteTime property).
If you try to run .DirectoryName.Replace($filepath,"") on a folder, it'll return such error as you cannot run replacing on $null.
Update: for PowerShell 2.0 you can use | where { ! $_.PSIsContainer } (source)
How can I troubleshoot it by myself?
In your error you can see which line is broken:
$NewPath = $File.DirectoryName.Replace($filepath,"")
All you have to do to troubleshoot such situations is to list all the involved variables and check their values. You could do it like this:
$File
$File.DirectoryName
Pause
$NewPath = $File.DirectoryName.Replace($filepath,"")
Using Pause can be useful as it'll wait for you to press Enter before continuing.

How to do custom file copy in Powershell?

Below power shell script will copy and replace all the contents from source to destination.
Copy-Item -Path $sourcePath -Destination $installationPath -Recurse -Force
This looks simple. But in my case, I need to implement a custom file copy logic.
Logging everything about files being copied.
Skipping certain set of folders.
Sample script:
[Array]$files=Get-ChildItem ($sourceDirectoryPath) -Recurse | Format-Table Name, Directory, FullName
for($i=0;$i -le $files.Length-1;$i++)
{
. . . . . .
# Build destination path based on the source path.
# Check and create folders if it doesn't exists
# Add if cases to skip certain parts.
Copy-Item -Force $sourcePath -Destination $destinationPath
}
Any ideas on how to achieve this? Any other ideas also will help.
Thanks.
Something like this:
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
$sourceDir = "c:\temp\source"
$targetDir = "c:\temp\target"
$skipFiles = #(
"skip.me",
"and.me"
)
Get-ChildItem -Path $sourceDir -Recurse | ForEach-Object {
# ignore folders
if ($_.PSIsContainer)
{
return
}
# skip this file?
if ($skipFiles -contains $_.Name)
{
Write-Verbose "Skipping '$_.FullName'"
return
}
# create target folder when needed
$path = $_.DirectoryName -replace "^$([RegEx]::Escape($sourceDir))",$targetDir
if (!(Test-Path $path))
{
Write-Verbose "Creating target path '$path'..."
New-Item -Path $path -ItemType Directory
}
# copy the file
Write-Verbose "Copying '$_.FullName' to '$path'..."
Copy-Item $_.FullName $path | Out-Null
}