Add folder to zip - powershell

I am facing a problem how to add folder to existing ZIP file.
This zip file is created by PowerShell also.
I can only use system classes provided by Powershell 5. I cannot use any of user packages or plugins (7zip included).
Here is my code:
function addFileToArchiveTest ($filePathToAdd, $archivePathToUpdate) {
if ([System.IO.File]::Exists($filePathToAdd) -or (Test-Path $filePathToAdd)) {
$file = [System.IO.Path]::GetFileName($filePathToAdd);
Write-Host $filePathToAdd.Name;
Write-Host $filePathToAdd;
Write-Host $archivePathToUpdate;
$archive = [System.IO.Compression.ZipFile]::Open($archivePathToUpdate, [System.IO.Compression.ZipArchiveMode]::Update);
$compressionLevel = [System.IO.Compression.CompressionLevel]::NoCompression;
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $filePathToAdd, $file, "$compressionLevel");
$archive.Dispose();
} else {
Write-Host "[ERROR#function] <AddFileToArchive>: <filePathToAdd> does not exist!";
Write-Host "[ERROR#function] <Variable<filePathToAdd>>: $filePathToAdd";
Write-Host "[ERROR#function] <Variable<archivePathToUpdate>>: $archivePathToUpdate";
}
}
I am thinking about variable $file - there might be a problem, because folder doesn't have an extension.
I run script like this:
PS> addFileToArchiveTest "C:\TestFolder\FolderToArchive" "C:\TestFolder\thereIsAlreadyZipFile.zip"
It returns with error:
Exception calling "CreateEntryFromFile" with "4" argument(s): "Access to the
path 'C:\TestFolder\FolderToArchive' is denied."
At C:\Users\user\Desktop\testfolder.ps1:196 char:13
+ [System.IO.Compression.ZipFileExtensions]::CreateEntryFro ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : UnauthorizedAccessException
Noted I also try allow script and I am launching with admin rights.

Perhaps surprisingly, CreateEntryFromFile() is for adding files, not folders. You need to add each file individually:
Get-ChildItem $filePathToAdd | ForEach-Object {
[IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $_.FullName, $_.Name, "$compressionLevel")
}

As user #guiwhatsthat answered: PowerShell 5 does support Compress-Archive. It does exactly what you want.
That is working as I want.

Related

Using CVS file to map network drives - PowerShell

I am developing a couple of PowerShell scripts to help speed up the process of migrating user data from an old workstation to a new one. Currently trying to make one to help with retrieving and then deploying mapped network drives and have hit a snagged with the deployment aspect. I am new to PowerShell and learning as I go along using the ISE to help spot some of the problem areas the script has. Here is a copy of what the script currently looks like and the error I am receiving when trying to run it on the machine.
# Import drive list.
$mappedDrives = Import-Csv C:\Users\########\Desktop\WIP_Scripts\MasterCopy\mappedDrives.csv
$mappedDrives | %{$_ -replace ":"}
foreach ($Name in $mappedDrives) {
New-PSDrive -Name $Name.Name -PSProvider FileSystem -Root "ProviderName" -Persist -ErrorAction Continue
}
Once I have it working Ill make the edits for where the import comes from. The errors I am currently receiving are:
New-PSDrive : Cannot process the drive name because the drive name contains one or more of
the following characters that are not valid: ; ~ / \ . :
At C:\Users\#######\Desktop\WIP_Scripts\MasterCopy\ImportMappedDrives.ps1:8 char:5
+ New-PSDrive -Name $Name.Name -PSProvider FileSystem -Root "Provid ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-PSDrive], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.NewPSDriveCommand
New-PSDrive : Cannot process the drive name because the drive name contains one or more of
the following characters that are not valid: ; ~ / \ . :
At C:\Users\#######\Desktop\WIP_Scripts\MasterCopy\ImportMappedDrives.ps1:8 char:5
+ New-PSDrive -Name $Name.Name -PSProvider FileSystem -Root "Provid ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-PSDrive], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.NewPSDriveCommand
New-PSDrive : Cannot process the drive name because the drive name contains one or more of
the following characters that are not valid: ; ~ / \ . :
At C:\Users\#######\Desktop\WIP_Scripts\MasterCopy\ImportMappedDrives.ps1:8 char:5
+ New-PSDrive -Name $Name.Name -PSProvider FileSystem -Root "Provid ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-PSDrive], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.NewPSDriveCommand
For the script used to retrieve the drive information:
$mappedDrives = #()
$Name = Get-WmiObject -ClassName Win32_MappedLogicalDisk | Select Name, ProviderName
foreach ($Name in $Name) {
if ($Name. ProviderName) {
$mappedDrives += Select-Object Name, ProviderName -InputObject $Name
}
}
$mappedDrives | Export-Csv mappedDrives.csv
MappedDrives.csv Output
Also attached is what the mappeddrives.csv output looks like for the retrieval. I thought that the csv file may be causing the invalid character arguements since the Name found within the csv file includes the ":" character. Also I am a bit confused on whether or not it will be able to see the "ProviderName" within the csv file or if I need to declare it in order for the script to add it to its argument. Again I am extremely new to Powershell so lots of what I have written down is what I have found from this site, Microsoft, or other blogs/forums and trying to Frankenstein together a working script. Any feedback on how to improve or get this to work and/or why using another method would be better in this situation would be greatly appreciated.
###Revision 1###
Utilizing the new script provided by RetiredGeek
# Import drive list.
$CFSArgs = #{PropertyNames = "Name", "ProviderName"
Delimiter = ','}
$MappedDrives = (Get-Content "G:\BEKDocs\Scripts\Test\mappedDrives.csv") |
ConvertFrom-String #CFSArgs
for ($cnt = 1; $cnt -lt $MappedDrives.count; $cnt++) {
$NPSDArgs =
#{Name = $(($MappedDrives[$cnt].Name).Substring(0,1))
PSProvider = "FileSystem"
Root = "$($MappedDrives[$cnt].ProviderName)"
Persist = $True
ErrorAction = "Continue"
}
New-PSDrive #NPSDArgs
}
I am now receiving the following error:
New-PSDrive : When you use the Persist parameter, the root must be a file system location
on a remote computer.
At C:\Users\######\Desktop\MasterCopy\Test2.ps1:16 char:9
+ New-PSDrive #NPSDArgs
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (":PSDriveInfo) [New-PSDrive], NotSupported
Exception
+ FullyQualifiedErrorId : DriveRootNotNetworkPath,Microsoft.PowerShell.Commands.NewPSD
riveCommand
The two questions I have now are:
Would it be more appropriate to use "Net use" instead of "New-PSDrive" for what I am trying to achieve(which is mapping a network drive to a computer using the cvs file created)?
If the use of the New-PSDrive cmdlet is a non-issue how do I rectify the error the script is currently outputting?
Thanks again to RetiredGeek and Theo for your inputs.
FT,
You need to evaluate your arguments in the New-PSDrive line.
Using Substring there eliminates code and makes it more efficient.
I had problems using Import-CSV so I switched to Get-Content and adjusted
# Import drive list.
$CFSArgs = #{PropertyNames = "Name", "ProviderName"
Delimiter = ','}
$MappedDrives = (Get-Content "G:\BEKDocs\Scripts\Test\mappedDrives.csv") |
ConvertFrom-String #CFSArgs
for ($cnt = 1; $cnt -lt $MappedDrives.count; $cnt++) {
$NPSDArgs =
#{Name = $(($MappedDrives[$cnt].Name).Substring(0,1))
PSProvider = "FileSystem"
Root = "$($MappedDrives[$cnt].ProviderName)"
Scope = "Global"
Persist = $True
ErrorAction = "Continue"
}
New-PSDrive #NPSDArgs -WhatIf
}
I used the -WhatIf parameter since I don't have your targets available but it showes what would have been done.
Output:
What if: Performing the operation "New drive" on target "Name: X Provider: Micro
soft.PowerShell.Core\FileSystem Root: \\hapi\desktop$\Decommission Log".
What if: Performing the operation "New drive" on target "Name: Y Provider: Micro
soft.PowerShell.Core\FileSystem Root: \\gonzo\Temp\AZ".
What if: Performing the operation "New drive" on target "Name: Z Provider: Micro
soft.PowerShell.Core\FileSystem Root: \\gonzo\Temp\001".
PS>
Update 1:
Further testing on my network (Peer-to-Peer) reveals that adding the Scope parameter (see above) will create the mapping, even though you get the same message, and it will last until you Reboot! It does NOT however persist after the reboot so it is not doing what it should. I still don't understand why it the message is displayed as the root is on another computer. Also, the mapping doesn't show in File Explorer although I can open a command prompt and successfully do a DIR on the drive.
Update 2:
I tried another test mapping to my Synology NAS and it worked w/o the error message. But, alas it did NOT persist a reboot!

System.IO.Compression.ZipFileExtensions - CreateEntryFromFile giving "because it is being used by another process" on zip

I am trying to compress 1 file into a new archive. The below code works.... most of the time. However, once in a while (network issues?) I changed the code (see below) to create a unique name, but I still occasionally get this error.
Is there any way to work around this problem? I'm on powershell 3 so compress-archive isn't an option, though I can probably get pscx installed.
Exception calling "Open" with "2" argument(s): "The process cannot access the file '\\path\share$\folder\filename_20200221_14__20200224_102143.zip' because it is being used by another process."
At line:3 char:5
+ [System.IO.Compression.ZipArchive]$ZipFile = [System.IO.Compression.ZipFile] ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : IOException
Here's the code I'm using.
I'm doing a "foreach ($file in $files_to_process)", but was able to duplicate the error by using a foreach {} to set variables, then run the below code. Subsequent runs are working, naturally.
$timestamp = (get-date).ToString("yyyyMMdd_HHmmss")
# Now create the zip file and remove the old one
[string]$zipFN = "$OnPremDirectoryDone$($file.basename)__$($timestamp).zip"
[string]$fileToZip = "$OnPremDirectory$($file.name)"
[System.IO.Compression.ZipArchive]$ZipFile = [System.IO.Compression.ZipFile]::Open($zipFN, ([System.IO.Compression.ZipArchiveMode]::Update))
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($ZipFile, $fileToZip, (Split-Path $fileToZip -Leaf))
$ZipFile.Dispose()
remove-item "$fileToZip"

Concatenate network path + variable (name of the folder)

How can I concatenate this network path with this variable entered by the user (it will be a full network path)?
So the user type the new folder name, for example: Folder-123 (will be stored in the variable $pjname)
Copy-Item "\\SERVER\Work_3rd\R Drive Structure\Project No\MDCXXXX" -Destination "\\SERVER\Work_3rd" -Recurse
write-host "Folder has been created. Press any key to continue..."
[void][System.Console]::ReadKey($true)
Write-Host "Please enter the project name: "
$pjname = Read-Host
Write-Output "New Folder will be: $pjname"
Rename-Item -Path "\\SERVER\Work_3rd\MDCXXXX" -NewName $pjname
write-host "Folder has been renamed. Press any key to continue..."
[void][System.Console]::ReadKey($true)
$pathToTemplate = '\\SERVER\Work_3rd\R Drive Structure\Project No\MDCXXXX'
$rootPath2 = '\\SERVER\Work_3rd\'
$rootPath = -join ($rootPath2, $pjname) # this concatenates the new project
name on to the root folder path**
# $rootPath += $pjname # this concatenates the new project name on to the
root folder path
If(Test-Path $rootPath)
{
$CurrentACL = (Get-Item $pathToTemplate).GetAccessControl('Access')
$CurrentACL | Set-Acl -Path $rootPath
}
This new folder stored in $pjname should have a network path like \\\SERVER\Work-3rd\ + FOLDER NAME. For example \\\SERVER\Word-3rd\Folder-123
The PowerShell is not finding the final path of the new folder so the permission is not being applied on it.
I'm trying in a test area and getting this problem below:
Folder has been renamed. Press any key to continue...
Get-Acl : Cannot find path '\\SERVER\test-area\Test-123' because it does not exist.
At C:\Users\felipe.sa\Desktop\Script\NewProjectFolder\NewProject-WP_-
_ProductionV3.ps1:279 char:8
+ $acl = Get-Acl $NewNetworkPath
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-Acl], ItemNotFoundException
+ FullyQualifiedErrorId :
GetAcl_PathNotFound_Exception,Microsoft.PowerShell.Commands.GetAclCommand
You cannot call a method on a null-valued expression.
At C:\Users\felipe.sa\Desktop\Script\NewProjectFolder\NewProject-WP_-
_ProductionV3.ps1:282 char:1
+ $acl.SetAccessRule($rule)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Are you trying to create a new directory in that share path, or rename a directory? It looks like you're trying to rename a directory.
My guess it isn't working because you are missing the trailing "\" in your path. Here is some sample code that adds a user supplied variable to a network path:
$MyRootPath = "\\SomeServer\Dir1\Dir2\"
Write-Host "Enter Dir Name"
$myAnswer = Read-Host
I typed "hello" when prompted for new directory..
$finalAnswer = $myAnswer.Trim()
$NewNetworkPath = ("{0}{1}" -f $MyRootPath, $finalAnswer)
$NewNetworkPath
returns:
\\SomeServer\Dir1\Dir2\hello
Whenever you are concatenating paths, escpecially those supplied by end users, stick to a utility that can do most of the heavy lifting. Use the combine method as string concatenation has several pitfalls that have to be needlessly mitigated.
[io.path]::combine('\\server\share', 'newfolder')
The combine method will take the path parts, as an array, and build a proper path. Note this does not validate if the path exists. It can deal with trailing path separators well. These next commands yield the same result.
[io.path]::combine('\\server\share\', 'newfolder')
[io.path]::combine('\\server\share', 'newfolder')
Beware leading path separators though:
If the one of the subsequent paths is an absolute path, then the combine operation resets starting with that absolute path, discarding all previous combined paths.
Thanks Matt & oze4! A weird problem happened now. I used the solution of oze4 and sometimes it works and sometimes not. Would it be the string entered by the user?
When it worked the folder name was 'MDX1111 - XXXX Xxxxxxx Xxxxxxxxx' - 32 characters. I ran the code again using the folder name 'MDX1112 - Xxxxxx Xxxxxxx Xxxxxxxxxx' - 35 characters and got this error below:
Get-Acl : Cannot find path '\\SERVER\Work_Block_A\MDX1112 - Xxxxxx Xxxxxxx
Xxxxxxxxxx' because it does not
exist.
At C:\Users\felipe.sa\Desktop\Script\NewProjectFolder\NewProject-WP_-
_ProductionV1.ps1:125 char:8
+ $acl = Get-Acl $NewNetworkPath
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-Acl], ItemNotFoundException
+ FullyQualifiedErrorId :
GetAcl_PathNotFound_Exception,Microsoft.PowerShell.Commands.GetAclCommand
You cannot call a method on a null-valued expression.
At C:\Users\felipe.sa\Desktop\Script\NewProjectFolder\NewProject-WP_-
_ProductionV1.ps1:128 char:1
+ $acl.SetAccessRule($rule)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Any thoughts?
Thank you.

Userprofile messed up

I have a little problem here with my user profile path for the use with powershell.
I have set my profile.ps1 to this:
$Shell = Host.UI.RawUI
$Shell.WindowTitle="PowerShell obeys me!"
$Shell.BackgroundColor="White"
$Shell.ForegroundColor="Blue"
$size = $Shell.WindowSize
$size.width=120
$size.height=50
$Shell.WindowSize = $size
$size = $Shell.BufferSize
$size.width=120
$size.height=5000
$Shell.BufferSize = $size
but everytime i execute run poweshell, it shows some erors like this one:
Property 'WindowTitle' cannot be found on this object; make sure it exists and is settable.
At D:\data\d7bighs\Documents\WindowsPowerShell\profile.ps1:5 char:8
+ $Shell. <<<< WindowTitle="PowerShell obeys me!"
+ CategoryInfo : InvalidOperation: (WindowTitle:String) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
now is i check my profile it tells me this:
$profile
d:\data\myusername\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
but if i check this through windows explorer it tells me this:
d:\User\myusername\Documents\WindowsPowerShell\profile.ps1
Im confused here because within explorer tells me d:\Useres but PS shows it as d:\data...
How can i change this or force PS to look after d:\users instead of d:\data?
a tiny error: $Shell = $Host.UI.RawUI, and you may need to create the profile first by
New-Item -Path $PROFILE -Type file
then edit it by
notepad $PROFILE

First parameter in PowerShell function loses its value?

Here's a function that I call with .\GetEMSInstallers. For some unknown reason the first parameter always loses its value:
function Get-EMSInstallers {
param (
$ems_for_amx_source = '\\server\ems_path',
$installers_dir = 'D:\installers'
)
process {
if (!(Test-Path "$installers_dir\EMS4AMX")) {
"Copying files and folders from $ems_for_amx_source to $installers_dir\EMS4AMX"
copy $ems_for_amx_source "$installers_dir\EMS4AMX" -rec -force
}
}
}
Get-EMSInstallers $args
When I call it I get this output:
Copying files and folders from to D:\installers\EMS4AMX
Copy-Item : Cannot bind argument to parameter 'Path' because it is an empty array.
At C:\Users\ad_ctjares\Desktop\Scripts\Ems\GetEMSInstallers.ps1:12 char:17
+ copy <<<< $ems_for_amx_source "$installers_dir\EMS4AMX" -rec -force
+ CategoryInfo : InvalidData: (:) [Copy-Item], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyArrayNotAllowed,Microsoft.PowerShell.Commands.CopyI
temCommand
When you don't pass in any argument to Get-EMSInstallers you still have a $args array - it is just empty. So the $ems_for_amx_source parameters is set to this empty array.
In other words, one way around this:
if ($args)
{
Get-EMSInstallers $args
}
else
{
Get-EMSInstallers
}
There is probably a more powershelly way to do this - I might revise this later if it comes to mind. :-) But that will get you started anyway.