Powershell create iso with defined directories - powershell

I'm using a function from Alistair McNair (https://github.com/TheDotSource/New-ISOFile/blob/main/New-ISOFile.ps1) as a starting point and trying to add some functionality to it. I would like to create some defined directories on iso that would then hold the data and copy specific data to that directory.
Here is the action to start the process:
$btnCreateISO.Add_Click({
$nicedate = get-date -UFormat %m-%d-%y #put in MM-DD-YY format
$isotitle = "WSUS-Offline-Server-$nicedate"
$isofilename = "WSUSSvrOffline-$nicedate.iso" #creates WSUS Offline Server such as update-11-14-2021.iso
$isofileandpath = $txtISOLocation.text+$isofilename # I:\WSUSSvrOffline-11-23-21.iso
$WSUSMetaDataPath = "c:\tools\wsusexport\"
$WSUSContentLocation = $txtWSUSContentLocation.Text # D:\WSUSContent\WsusContent that holds files
#ProgressBar information
$ProgressBar.Visible = $true
$ProgressBar.MarqueeAnimationSpeed = 20
$btnCreateISO.text = "Exporting"
Start-process -FilePath $txtWSUSUtil.text -ArgumentList #("export","$WSUSMetaDataPath\$nicedate-export.xml.gz","$WSUSMetaDataPath\$nicedate-export.log") -Wait
$btnCreateISO.text = "Exporting Completed"
$btnCreateISO.text = "Creating ISO"
New-ISOFile -WSUSContentLocation $WSUSContentLocation -WSUSMetaDataLocation $WSUSMetaDataPath -destinationIso $isofileandpath -title $isotitle -force -WhatIf
$btnCreateISO.text = "ISO Created"
Here is the code for the function:
...
function New-ISOFile {
#file originall from https://github.com/TheDotSource/New-ISOFile/blob/main/New-ISOFile.ps1 by Alistair McNair
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Low")]
Param
(
[parameter(Mandatory=$true,ValueFromPipeline=$false)]
[string]$WSUSContentLocation,
[parameter(Mandatory=$true,ValueFromPipeline=$false)]
[string]$WSUSMetaDataLocation,
[parameter(Mandatory=$true,ValueFromPipeline=$false)]
[string]$destinationIso,
[Parameter(Mandatory=$false,ValueFromPipeline=$false)]
[string]$title = "untitled",
[Parameter(Mandatory=$false,ValueFromPipeline=$false)]
[switch]$force
)
begin {
Write-Verbose ("Function start.")
} # begin
process {
Write-Verbose ("Processing nested system " + $vmName)
## Set type definition
Write-Verbose ("Adding ISOFile type.")
$typeDefinition = #'
public class ISOFile {
public unsafe static void Create(string Path, object Stream, int BlockSize, int TotalBlocks) {
int bytes = 0;
byte[] buf = new byte[BlockSize];
var ptr = (System.IntPtr)(&bytes);
var o = System.IO.File.OpenWrite(Path);
var i = Stream as System.Runtime.InteropServices.ComTypes.IStream;
if (o != null) {
while (TotalBlocks-- > 0) {
i.Read(buf, BlockSize, ptr); o.Write(buf, 0, bytes);
}
o.Flush(); o.Close();
}
}
}
'#
## Create type ISOFile, if not already created. Different actions depending on PowerShell version
if (!('ISOFile' -as [type])) {
## Add-Type works a little differently depending on PowerShell version.
## https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type
switch ($PSVersionTable.PSVersion.Major) {
## 7 and (hopefully) later versions
{$_ -ge 7} {
Write-Verbose ("Adding type for PowerShell 7 or later.")
Add-Type -CompilerOptions "/unsafe" -TypeDefinition $typeDefinition
} # PowerShell 7
## 5, and only 5. We aren't interested in previous versions.
5 {
Write-Verbose ("Adding type for PowerShell 5.")
$compOpts = New-Object System.CodeDom.Compiler.CompilerParameters
$compOpts.CompilerOptions = "/unsafe"
Add-Type -CompilerParameters $compOpts -TypeDefinition $typeDefinition
} # PowerShell 5
default {
## If it's not 7 or later, and it's not 5, then we aren't doing it.
throw ("Unsupported PowerShell version.")
} # default
} # switch
} # if
## Initialise image
Write-Verbose ("Initialising image object.")
try {
$image = New-Object -ComObject IMAPI2FS.MsftFileSystemImage -Property #{VolumeName=$title} -ErrorAction Stop
Write-Verbose ("initialised.")
} # try
catch {
throw ("Failed to initialise image. " + $_.exception.Message)
} # catch
## Create target ISO, throw if file exists and -force parameter is not used.
if ($PSCmdlet.ShouldProcess($destinationIso)) {
if (!($targetFile = New-Item -Path $destinationIso -ItemType File -Force:$Force -ErrorAction SilentlyContinue)) {
throw ("Cannot create file " + $destinationIso + ". Use -Force parameter to overwrite if the target file already exists.")
} # if
} # if
## Get WSUSMetaData content from specified path
Write-Verbose ("Fetching items from WSUSMetaData directory.")
try {
$WSUSMetaData = Get-ChildItem -LiteralPath $WSUSMetaDataLocation -ErrorAction Stop
Write-Verbose ("Got WSUSMetaData items.")
} # try
catch {
throw ("Failed to get WSUSMetaData items. " + $_.exception.message)
} # catch
## Get WSUSMetaData content from specified path
Write-Verbose ("Fetching items from WSUSContentData directory.")
try {
$WSUSContentData = Get-ChildItem -LiteralPath $WSUSContentLocation -ErrorAction Stop
Write-Verbose ("Got WSUSContentData items.")
} # try
catch {
throw ("Failed to get WSUSContentData items. " + $_.exception.message)
} # catch
##Create Directories on the ISO for the content
## Both the WSUSMetaData files go into this directory on the iso
$DirWSUSMetaData = $image.CreateDirectoryItem("WSUSMetaData")
$DirWSUSMetaData = $image.Root.AddTree($WSUSMetaDataLocation,$true)
## Add the WSUSMetaData to our image
Write-Verbose ("Adding WSUSMetaData to image.")
foreach($file in $WSUSMetaDataLocation){
try {
$image.Root.AddTree($file.fullname, $true)
}
catch {
throw ("Faile to add" + $file.fullname + ". " + $_.exeption.message)
}
}
##All the WSUSContent directories go into this directory on the iso
$DirWSUSContent = $image.CreateDirectoryItem("WSUSContentData") #create the directory WSUSContentData to hold all WSUS Content in the ISO
$DirWSUSContent = $image.Root.AddTree($WSUSContentLocation,$true) #add the location of the content to copy
## Add the WSUSMetaData to our image
Write-Verbose ("Adding WSUSMetaData to image.")
foreach($file in $WSUSContentLocation) {
try {
$image.Root.AddTree($file.FullName, $true)
} # try
catch {
throw ("Failed to add " + $file.fullname + ". " + $_.exception.message)
} # catch
} # foreach
## Write out ISO file
Write-Verbose ("Writing out ISO file to " + $targetFile)
try {
$result = $image.CreateResultImage()
[ISOFile]::Create($targetFile.FullName,$result.ImageStream,$result.BlockSize,$result.TotalBlocks)
} # try
catch {
throw ("Failed to write ISO file. " + $_.exception.Message)
} # catch
Write-Verbose ("File complete.")
## Return file details
return $targetFile
} # process
end {
Write-Verbose ("Function complete.")
} # end
...
Getting an error when I'm trying to create it:
What if: Performing the operation "New-ISOFile" on target " WSUSSvrOffline-11-23-21.iso".
Adding '11-23-21-export.xml.gz' would result in a result image having a size larger than the current configured limit.
At C:\tools\WSUSPowerShellStuff\WSUSOfflineServerGUIv1.ps1:153 char:9
+ $DirWSUSMetaData = $image.Root.AddTree($WSUSMetaDataLocation, ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
Faile to add.
At C:\tools\WSUSPowerShellStuff\WSUSOfflineServerGUIv1.ps1:162 char:14
+ ... throw ("Faile to add" + $file.fullname + ". " + $_.exepti ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Faile to add. :String) [], RuntimeException
+ FullyQualifiedErrorId : Faile to add.
So how do you change the configured limit?
Files Size of file giving error

Related

Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process

I have the following PowerShell script:
param([switch]$Elevated)
function Test-Admin
{
$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if ((Test-Admin) -eq $false) {
if ($elevated) {
# tried to elevate, did not work, aborting
} else {
Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated ' -f ($myinvocation.MyCommand.Definition))
}
exit
}
function UpdateHosts {
param ($hostName)
Write-Host $hostName
try {
$strHosts = (Get-Content C:\WINDOWS\system32\drivers\etc\hosts -Raw)
if([string]::IsNullOrEmpty($strHosts)) {
Write-Error "Get-Content hosts empty"
exit
}
} catch {
Write-Error "Unable to read hosts file"
Write-Error $_
exit
}
try {
$strHosts -replace "[\d]+\.[\d]+\.[\d]+\.[\d]+ $hostName","$ipAddress $hostName" | Set-Content -Path C:\WINDOWS\system32\drivers\etc\hosts
} catch {
Write-Error "Unable to write hosts file"
Write-Error $_
exit
}
}
$ipAddress = "127.0.0.1"
UpdateHosts -hostName local.pap360.com
Sometimes, when I run it, I get the following error:
Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process.
When I open up C:\WINDOWS\system32\drivers\etc\hosts in Notepad it's then blank. ie. all the data I had in it is wiped.
My question is... how can I prevent this from happening?
Like if Set-Content can't access the hosts file to write to it then how is it able to wipe it's contents? And why isn't the catch block working?
Here's the full error:
Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by
another process.
At C:\path\to\test.ps1:36 char:92
+ ... $hostName" | Set-Content -Path C:\WINDOWS\system32\drivers\etc\hosts
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\WINDOWS\system32\drivers\etc\hosts:String) [Set-Content], IOException
+ FullyQualifiedErrorId : GetContentWriterIOError,Microsoft.PowerShell.Commands.SetContentCommand
I also don't understand why it's so intermittent. Is there some Windows process that opens the hosts file up for 1s once a minute or some such?
First of all, check if your Firewall or AV software isn't restricting access to the file.
If that is not the case and 'some' other process is currently locking the hosts file, perhaps add a test before reading or writing the file can help:
function Test-LockedFile {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName', 'FilePath')]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$Path
)
$file = [System.IO.FileInfo]::new($Path)
# old PowerShell versions use:
# $file = New-Object System.IO.FileInfo $Path
try {
$stream = $file.Open([System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
return $false # file is not locked
}
catch {
return $true # file is locked
}
}
Then use like this:
function UpdateHosts {
param ($hostName)
Write-Host $hostName
$path = 'C:\WINDOWS\system32\drivers\etc\hosts'
# test if the file is readable/writable
# you can of course also put this in a loop to keep trying for X times
# until Test-LockedFile -Path $path returns $false.
if (Test-LockedFile -Path $path) {
Write-Error "The hosts file is currently locked"
}
else {
try {
$strHosts = (Get-Content $path -Raw -ErrorAction Stop)
if([string]::IsNullOrEmpty($strHosts)) {
Write-Error "Get-Content hosts empty"
exit
}
}
catch {
Write-Error "Unable to read hosts file:`r`n$($_.Exception.Message)"
exit
}
try {
$strHosts -replace "[\d]+\.[\d]+\.[\d]+\.[\d]+\s+$hostName", "$ipAddress $hostName" |
Set-Content -Path $path -Force -ErrorAction Stop
}
catch {
Write-Error "Unable to write hosts file:`r`n$($_.Exception.Message)"
exit
}
}
}

Powershell File Lock function is not opening excel File

I've written a function to check if an excel file is being used/locked by another process/user in a shared network drive, and if used, to pause the script and keep checking till it's available as the next action is to move it out of it's folder. However when I'm using System.IO to read the file, it does not open the file. I've tested on my local drive and this does open the file, but does this not work in Network Drives?
$IsLocked = $True
Function Test-IsFileLocked {
[cmdletbinding()]
Param (
[parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName','PSPath')]
[string[]]$Path
)
Process {
while($isLocked -eq $True){
If ([System.IO.File]::Exists($Path)) {
Try {
$FileStream = [System.IO.File]::Open($Path,'Open','Write')
$FileStream.Close()
$FileStream.Dispose()
$IsLocked = $False
} Catch {
$IsLocked = $True
echo "file in use, trying again in 10 secs.."
Start-Sleep -s 10
}
}
}
}
}
This is where the code does not pick up/open the excel file in my function
$FileStream = [System.IO.File]::Open($Path,'Open','Write')
This is where the program calls the function.Loop through a folder of items in the network drive and if the item matches the pattern, then the function will be called to check if the file is in use:
$DirectoryWithExcelFile = Get-ChildItem -Path "Z:\NetworkDriveFolder\"
$DestinationFolder = "Z:\DestinationFolder\"
$pattern = "abc"
foreach($file in $DirectoryWithExcelFile){
if($file.Name -match $pattern){
Test-IsFileLocked -Path $file
$destFolder = $DestinationFolder+$file.Name
Move-item $file.FullName -destination $destFolder
break
}
}
You have to place the close and dispose in the finally part of the try so if it throws an exception it disposes of the lock. There's no guarantee it's a file lock exception either so you are better to throw the exception, catch the exception you're expecting or write it out
$IsLocked = $True
Function Test-IsFileLocked {
[cmdletbinding()]
Param (
[parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName','PSPath')]
[string]$Path
)
Process {
while($isLocked -eq $True){
If ([System.IO.File]::Exists($Path)) {
Try {
$FileStream = [System.IO.File]::Open($Path,'Open','Write')
$IsLocked = $False
} Catch [System.IO.IOException] {
$IsLocked = $True
echo "file in use, trying again in 10 secs.."
Start-Sleep -s 10
} Catch {
# source https://stackoverflow.com/questions/38419325/catching-full-exception-message
$formatstring = "{0} : {1}`n{2}`n" +
" + CategoryInfo : {3}`n" +
" + FullyQualifiedErrorId : {4}`n"
$fields = $_.InvocationInfo.MyCommand.Name,
$_.ErrorDetails.Message,
$_.InvocationInfo.PositionMessage,
$_.CategoryInfo.ToString(),
$_.FullyQualifiedErrorId
$formatstring -f $fields
write-output $formatstring
} finally {
if($FileStream) {
$FileStream.Close()
$FileStream.Dispose()
}
}
}
}
}
}
Edit:
Path should be a string, not a string array unless you have multiple paths in which case rename to $Paths and loop through them $Paths| % { $Path = $_; #do stuff here }

Positioning of test-path in a PowerShell script

I have a script that sends an email when files are about to reach their max size.
The problem is, that I can't implement a validation check of what the script should do when a file is not found at the given path.
I know that I should implement:
if(!(Test-Path $item.Path))
{
"The file " + $item.Name + " is not found! <br />"
}
But I don't know where to place it exactly (since I'm getting errors all over the place).
The Code:
I start with retrieving the files from a config file:
$list = foreach ($file in $files)
{
$f = Get-Item $file.Path
New-Object -Type PSObject -Property #{
Name = $f.Name
Path = $file.Path
Size = ($f.Length/$byteSize)
}
}
then I loop through the file list and check their size:
foreach ($item in $list)
{
if($item.Size -gt $item.TriggerSize -And (Test-Path $item.Path))
{
#Write-Host $item.Name "Warning! File has reached it's max limit ("$item.Size.ToString(".00")"), The max size is" $item.MaxSize". The trigger size is: " $item.TriggerSize "`r`n"
$body +=
"The file " + $item.Name + " is reaching his max size!
<br /> Current size: <b>"+$item.Size.ToString(".00") + " " + $byteSize.Substring(1) + "</b>" +
"<br /> Maximum size: " + $item.MaxSize + " " + $byteSize.Substring(1) + "<br /><br />"
}
}
I tried implementing my error check on these places:
$list = foreach ($file in $files)
{
if(!(Test-Path $item.Path))
{
"The file " + $item.Name + " is not found! <br />"
}
$f = Get-Item $file.Path
New-Object -Type PSObject -Property #{
Name = $f.Name
Path = $file.Path
Size = ($f.Length/$byteSize)
}
}
and here
foreach ($item in $list)
{
if($item.Size -gt $item.TriggerSize -And (Test-Path $item.Path))
{
#Write-Host $item.Name "Warning! File has reached it's max limit ("$item.Size.ToString(".00")"), The max size is" $item.MaxSize". The trigger size is: " $item.TriggerSize "`r`n"
$body +=
"The file " + $item.Name + " is reaching his max size!
<br /> Current size: <b>"+$item.Size.ToString(".00") + " " + $byteSize.Substring(1) + "</b>" +
"<br /> Maximum size: " + $item.MaxSize + " " + $byteSize.Substring(1) + "<br /><br />"
}
if(!(Test-Path $item.Path))
{
"The file " + $item.Name + " is not found! <br />"
}
}
But I keep getting the error that it's at Get-Item that it can't find a file:
the name of the file that I wrote with a false path on purpose:
file123.exe
Error (at the line of the if(!(Test-Path $file.path)):
Get-Item : Cannot find path 'file123.exe' because it does not exist.
You are trying to error-handle but then your code still passes control to main logic. Instead, you should develop your control flow so that Get-Item will be called only when Test-Path returns true.
$list = foreach ($file in $files)
{
if (Test-Path $file.Path) {
$f = Get-Item $file.Path
New-Object -Type PSObject -Property #{
Name = $f.Name
Path = $f.Path
Size = ($f.Length/$byteSize)
}
} else {
"The file " + $file.path + " is not found! <br />"
}
}
And finally, PROOFREAD YOUR CODE! You've got $item.path tested but $file.path accessed. Given your foreach loop, you should have $file.path at both places.
You are only checking for file's existence and not catching not found exception. Here's a pseudo that should work:
foreach ($file in $files) {
try {
if (!(Test-Path $file.Path)) {
throw "File $($file.Path) not found"
}
# Rest of the logic
}
catch {
# log error $_.Exception.Message
}
}
Update: Send email whether file exists or not. This is pseudo, excuse syntax please.
Refactor the email sending functionality in separate method, say Send-Email($content). Then:
foreach file in files {
if !(Test-Path file.path) {
Send-Email -content "File $($file) does does not exist"
}
else {
// your logic here
if file.size > threshold {
Send-Email -content "File $($file) exceeded threshold size"
}
}
}

check for text in a file on a remote machine

I am using a powershell to query a file on a remote machines C-drive and if the file exist with status 'imaging completed' it should run other checks.
$filetofind = Get-Content C:\Image.log
#Get the list down to just imagestatus and export
foreach ($line in $filetofind)
{
$file = $line.trim("|")
echo $file >> C:\jenkins\imagestatus.txt
}
But when I run below commands I am getting the error.
Can anyone help ?
Get-Content : Cannot find path 'C:\Image.log' because it does not exist.
At line:18 char:15
+ $filetofind = Get-Content C:\Image.log
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Image.log:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Test-Path will check if a file exists, and Select-String can be used to search the file for a string, using the -Quiet param will make the command return True if the string is found, rather than returning each line in the text file that includes the string.
Then using both commands in simple if statements to check their status:
$file = "C:\Image.log"
$searchtext = "imaging completed"
if (Test-Path $file)
{
if (Get-Content $file | Select-String $searchtext -Quiet)
{
#text exists in file
}
else
{
#text does not exist in file
}
}
else
{
#file does not exist
}
EDIT:
To check the file on multiple computers you need to use a foreach loop to run the code against each computer separately. The below assumes you have one hostname per line in hostnames.txt.
$hostnames = Get-Content "C:\hostnames.txt"
$searchtext = "imaging completed"
foreach ($hostname in $hostnames)
{
$file = "\\$hostname\C$\GhostImage.log"
if (Test-Path $file)
{
if (Get-Content $file | Select-String $searchtext -quiet)
{
Write-Host "$hostname: Imaging Completed"
}
else
{
Write-Host "$hostname: Imaging not completed"
}
}
else
{
Write-Host "$hostname: canot read file: $file"
}
}

Powershell Zip Folder Contents

I know a lot has been covered on zipping files using Powershell however I cannot find a method which does exactly what I need.
I want to be able to zip folders AND files into a .zip folder without having the parent folder inside the zip. So for example I have a folder named STUFF which contains files/folders, I want to zip this into a folder called STUFF.zip. This folders structure would then be STUFF.zip>files/folders NOT STUFF.zip>STUFF>files/folders as I currently get using this code...
function CountZipItems(
[__ComObject] $zipFile)
{
If ($zipFile -eq $null)
{
Throw "Value cannot be null: zipFile"
}
Write-Host ("Counting items in zip file (" + $zipFile.Self.Path + ")...")
[int] $count = CountZipItemsRecursive($zipFile)
Write-Host ($count.ToString() + " items in zip file (" `
+ $zipFile.Self.Path + ").")
return $count
}
function CountZipItemsRecursive(
[__ComObject] $parent)
{
If ($parent -eq $null)
{
Throw "Value cannot be null: parent"
}
[int] $count = 0
$parent.Items() |
ForEach-Object {
$count += 1
If ($_.IsFolder -eq $true)
{
$count += CountZipItemsRecursive($_.GetFolder)
}
}
return $count
}
function IsFileLocked(
[string] $path)
{
If ([string]::IsNullOrEmpty($path) -eq $true)
{
Throw "The path must be specified."
}
[bool] $fileExists = Test-Path $path
If ($fileExists -eq $false)
{
Throw "File does not exist (" + $path + ")"
}
[bool] $isFileLocked = $true
$file = $null
Try
{
$file = [IO.File]::Open(
$path,
[IO.FileMode]::Open,
[IO.FileAccess]::Read,
[IO.FileShare]::None)
$isFileLocked = $false
}
Catch [IO.IOException]
{
If ($_.Exception.Message.EndsWith(
"it is being used by another process.") -eq $false)
{
Throw $_.Exception
}
}
Finally
{
If ($file -ne $null)
{
$file.Close()
}
}
return $isFileLocked
}
function GetWaitInterval(
[int] $waitTime)
{
If ($waitTime -lt 1000)
{
return 100
}
ElseIf ($waitTime -lt 5000)
{
return 1000
}
Else
{
return 5000
}
}
function WaitForZipOperationToFinish(
[__ComObject] $zipFile,
[int] $expectedNumberOfItemsInZipFile)
{
If ($zipFile -eq $null)
{
Throw "Value cannot be null: zipFile"
}
ElseIf ($expectedNumberOfItemsInZipFile -lt 1)
{
Throw "The expected number of items in the zip file must be specified."
}
Write-Host -NoNewLine "Waiting for zip operation to finish..."
Start-Sleep -Milliseconds 1000 # ensure zip operation had time to start
[int] $waitTime = 0
[int] $maxWaitTime = 60 * 1000 # [milliseconds]
while($waitTime -lt $maxWaitTime)
{
[int] $waitInterval = GetWaitInterval($waitTime)
Write-Host -NoNewLine "."
Start-Sleep -Milliseconds $waitInterval
$waitTime += $waitInterval
Write-Debug ("Wait time: " + $waitTime / 1000 + " seconds")
[bool] $isFileLocked = IsFileLocked($zipFile.Self.Path)
If ($isFileLocked -eq $true)
{
Write-Debug "Zip file is locked by another process."
Continue
}
Else
{
Break
}
}
Write-Host
If ($waitTime -ge $maxWaitTime)
{
Throw "Timeout exceeded waiting for zip operation"
}
[int] $count = CountZipItems($zipFile)
If ($count -eq $expectedNumberOfItemsInZipFile)
{
Write-Debug "The zip operation completed succesfully."
}
ElseIf ($count -eq 0)
{
Throw ("Zip file is empty. This can occur if the operation is" `
+ " cancelled by the user.")
}
ElseIf ($count -gt $expectedCount)
{
Throw "Zip file contains more than the expected number of items."
}
}
function ZipFolder(
[IO.DirectoryInfo] $directory)
{
If ($directory -eq $null)
{
Throw "Value cannot be null: directory"
}
Write-Host ("Creating zip file for folder (" + $directory.FullName + ")...")
[IO.DirectoryInfo] $parentDir = $directory.Parent
[string] $zipFileName
If ($parentDir.FullName.EndsWith("\") -eq $true)
{
# e.g. $parentDir = "C:\"
$zipFileName = $parentDir.FullName + $directory.Name + ".zip"
}
Else
{
$zipFileName = $parentDir.FullName + "\" + $directory.Name + ".zip"
#$zipFileName = $directory.Name + ".zip"
#$zipFileName = $parentDir.FullName + ".zip"
}
If (Test-Path $zipFileName)
{
Throw "Zip file already exists ($zipFileName)."
}
Set-Content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$shellApp = New-Object -ComObject Shell.Application
$zipFile = $shellApp.NameSpace($zipFileName)
If ($zipFile -eq $null)
{
Throw "Failed to get zip file object."
}
[int] $expectedCount = (Get-ChildItem $directory -Force -Recurse).Count
$expectedCount += 1 # account for the top-level folder
$zipFile.CopyHere($directory.FullName)
#Get-ChildItem $directory | foreach {$zipFile.CopyHere($_.fullname)}
# wait for CopyHere operation to complete
WaitForZipOperationToFinish $zipFile $expectedCount
Write-Host -Fore Green ("Successfully created zip file for folder (" `
+ $directory.FullName + ").")
}
Usage
Remove-Item "H:\STUFF.zip"
[IO.DirectoryInfo] $directory = Get-Item "H:\STUFF"
ZipFolder $directory
Complete credit goes here for this code. I really appreciate any help I get, this capability is crucial to my project! Unfortunately I cannot use the Community Extension module as other PC's this will be run on does not have this module installed.
Thanks!
As per #mjolinor "H:\Stuff\", if you have .NET 4.5 installed you can run the following:
$src = "H:\Stuff\"
$dst = "H:\Stuff.zip"
[Reflection.Assembly]::LoadWithPartialName( "System.IO.Compression.FileSystem" )
[System.IO.Compression.ZipFile]::CreateFromDirectory($src, $dst)
New solution available with WMF 5 (well not that new ^^)
Compress-Archive -Path H:\stuff\* -DestinationPath H:\stuff.zip -Force
If you use PSCX Module you will use the line code below to obtain what you need:
write-zip h:\Stuff\* myzipfile.zip
I'm developing a simple module in Powershell, to more details https://github.com/julianosaless/powershellzip