Tweak an unzip script

synetron
synetron used Ask the Experts™
on
When i run:
function Extract-Zip
{
	param([string]$zipfilename, [string] $destination)

	if(test-path($zipfilename))
	{	
		$shellApplication = new-object -com shell.application
		$zipPackage = $shellApplication.NameSpace($zipfilename)
		$destinationFolder = $shellApplication.NameSpace($destination)
		$destinationFolder.CopyHere($zipPackage.Items())
	}
}


#find all MAC files in the immediate directory
dir "C:\script\test\ftp\MAC*.zip" | foreach-object{

	extract-zip $_ c:\script\test\Master\MAC
	extract-zip $_ c:\script\test\Working\MAC

}

Move-Item "C:\script\test\ftp\MAC*" "C:\script\test\ftp\archive"

#find all MAH files in the immediate directory
dir "C:\script\test\ftp\MAH*.zip" | foreach-object{

	extract-zip $_ c:\script\test\Master\MAH\
	extract-zip $_ c:\script\test\Working\MAH\

}

Move-Item "C:\script\test\ftp\MAH*" "C:\script\test\ftp\archive"

#find all MAM files in the immediate directory
dir "C:\script\test\ftp\MAM*.zip" | foreach-object{

	extract-zip $_ c:\script\test\Master\MAM
	extract-zip $_ c:\script\test\Working\MAM

}

Move-Item "C:\script\test\ftp\MAM*" "C:\script\test\ftp\archive"

Open in new window


This works flawlessly.

I need however to arrange the destination directory to receive the extracted files in a unique sub-directory.

Ideally i would arranged them based on characters 5 through 12 of the original zip file name, if however i can only use the original zip file name without the .zip extention as the name of my directory i would consider it a success.

For any verterans out there looking to help me out i obviously get a
You cannot call a method on a null-valued expression.
At C:\script\test\unzip_archive1.ps1:10 char:30
+         $destinationFolder.CopyHere <<<< ($zipPackage.Items())
    + CategoryInfo          : InvalidOperation: (CopyHere:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull
when i add "\$_" to the end of my extraction path.

as for any rookies like myself, this isn't the solution... :-(

also, should I
Move-Item $_ "C:\script\test\ftp\archive" within my foreach-object{
} to make it cleaner or just wait until all have completed before moving all such items to the archive? and can I Move-Item $_ within the foreach statement?

Thanks
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Meir RivkinFull stack Software Engineer

Commented:
try add $_.BaseName to the end of my extraction path.
Meir RivkinFull stack Software Engineer

Commented:
also on lines 18-19 you call the extract-zip with the same file but different destination.
i think u should simply copy the files to the other destination once the first call to extract-zip is done.

Author

Commented:
Thank you for the reply. I added $_.BaseName to the end of the extraction paths and it returned:

You cannot call a method on a null-valued expression.
At C:\script\test\test.ps1:10 char:30
+         $destinationFolder.CopyHere <<<< ($zipPackage.Items())
    + CategoryInfo          : InvalidOperation: (CopyHere:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

As for lines 18-19 and moving to the other destination after the first extraction is complete, this doesn't fit the current need to archive for backup and have two working extracted copies ready for the following morning.
OWASP: Forgery and Phishing

Learn the techniques to avoid forgery and phishing attacks and the types of attacks an application or network may face.

Meir RivkinFull stack Software Engineer

Commented:
try create the destination folder before doing the actual unzipping inside the function.
function Extract-Zip
{
	param([string]$zipfilename, [string] $destination)

	if(test-path($zipfilename))
	{	md $destination #creates destination folder
		$shellApplication = new-object -com shell.application
		$zipPackage = $shellApplication.NameSpace($zipfilename)
		$destinationFolder = $shellApplication.NameSpace($destination)
		$destinationFolder.CopyHere($zipPackage.Items())
	}
}

Open in new window

Author

Commented:
Well that was interesting...

New-Item : The given path's format is not supported.
At line:38 char:24
+         $scriptCmd = {& <<<<  $wrappedCmd -Type Directory @PSBoundParameters }
    + CategoryInfo          : InvalidOperation: (C:\script\test\...py.zip.BaseName:String) [New-Item], NotSupportedExc
   eption
    + FullyQualifiedErrorId : ItemExistsNotSupportedError,Microsoft.PowerShell.Commands.NewItemCommand

You cannot call a method on a null-valued expression.
At C:\script\test\mema.ps1:11 char:30
+         $destinationFolder.CopyHere <<<< ($zipPackage.Items())
    + CategoryInfo          : InvalidOperation: (CopyHere:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Author

Commented:
unless I'm mistaken, the inability to designate the destination lies in trying to use a variable (pardon the ignorance in accurate terms) like $_ or $_.BaseName in the extraction section (lines 18, 19, for example) since the extract-zip is a call to the function and thus the $_ which typically would reference the file being currently processed (by the for each section) has a null value as it's an argument being processed within the function...

If that is correct, then i should create the directory before i extract the data to it within the ForEach with something like "MD <PATH TO DIRECTORY>\$_.BaseName" but still have no way to call upon the name of the subdirectory i just created within my extract-zip function as this will be unique to each file being extracted.  

Could this possibly be resolved by listing the currently processed file name as a string
$a = $_.BaseName
who's value can be accessed both in my for each statements and passed to the function? could a variable be set to represent this value... if so, how? could this type of statement be made before the function and stored (thinking object oriented here... ) outside of the function and outside of the ForEach sections thus being available to both or would the $_ only work when it's given life by the ForEach section of the script?

I can't help but feel I'm so close, but my limitation in experience (this is my first ever PS script) is killing me.
Meir RivkinFull stack Software Engineer

Commented:
i probably missing something here cause if the initial script is working, it should work if only thing is to change the destination folder based on the file name.

can u put write-host $destination inside the function?
also use test-path before creating the folder:
function Extract-Zip
{
	param([string]$zipfilename, [string] $destination)
write-host $destination
	if(test-path($zipfilename))
	{	
if((Test-Path $destination) -eq $False){
md $destination #creates destination folder
}
		$shellApplication = new-object -com shell.application
		$zipPackage = $shellApplication.NameSpace($zipfilename)
		$destinationFolder = $shellApplication.NameSpace($destination)
		$destinationFolder.CopyHere($zipPackage.Items())
	}
}
                                            

Open in new window

Author

Commented:
Thank you sedgwick for your continued thoughts on the matter. I attempted this in various methods with no luck.
1. the destination directory does not create
2. the files still extract to the destination path without a unique sub-directory for each

mostly, this is what I'm seeing now:
c:\script\test\Master\MAH\C:\script\test\ftp\MAH.20120213-02.20.01 - Copy.zip
Test-Path : The given path's format is not supported.
At C:\script\test\mema.ps1:7 char:14
+ if((Test-Path <<<<  $destination) -eq $False){
    + CategoryInfo          : InvalidOperation: (C:\script\test\...0.01 - Copy.zip:String) [Test-Path], NotSupportedEx
   ception
    + FullyQualifiedErrorId : ItemExistsNotSupportedError,Microsoft.PowerShell.Commands.TestPathCommand

New-Item : The given path's format is not supported.
At line:38 char:24
+         $scriptCmd = {& <<<<  $wrappedCmd -Type Directory @PSBoundParameters }
    + CategoryInfo          : InvalidOperation: (C:\script\test\...0.01 - Copy.zip:String) [New-Item], NotSupportedExc
   eption
    + FullyQualifiedErrorId : ItemExistsNotSupportedError,Microsoft.PowerShell.Commands.NewItemCommand

You cannot call a method on a null-valued expression.
At C:\script\test\mema.ps1:13 char:30
+         $destinationFolder.CopyHere <<<< ($zipPackage.Items())
    + CategoryInfo          : InvalidOperation: (CopyHere:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull
Meir RivkinFull stack Software Engineer

Commented:
the concatenation of the $destination folder is wrong.
that's why u get:
c:\script\test\Master\MAH\C:\script\test\ftp\MAH.20120213-02.20.01 - Copy.zip

instead of:
c:\script\test\Master\MAH\MAH.20120213-02.20.01 - Copy.zip

please post the whole script again, i'll test it on my machine and make it work.

Author

Commented:
sedgwick, I can't thank you enough for your help, i believe I've had a breakthrough. I'll repost the script that I now have working...

function Extract-Zip
{
	param([string]$zipfilename, [string] $destination)

	if(test-path($zipfilename))
	{	
		$shellApplication = new-object -com shell.application
		$zipPackage = $shellApplication.NameSpace($zipfilename)
		$destinationFolder = $shellApplication.NameSpace($destination)
		$destinationFolder.CopyHere($zipPackage.Items())
	}
}


#find all MAC files in the immediate directory
dir "C:\script\test\ftp\MAC*.zip" | foreach-object{

#create directory	
	$id = $_.Name.SubString(4,8); 
    if(-not (Test-Path c:\script\test\Master\MAC\$id)) {mkdir c:\script\test\Master\MAC\$id}; 

#extract	
	extract-zip $_ c:\script\test\Master\MAC\$id
	
#create directory
    if(-not (Test-Path c:\script\test\Working\MAC\$id)) {mkdir c:\script\test\Working\MAC\$id}; 

#extract
	extract-zip $_ c:\script\test\Working\MAC\$id

}

#clean up to avoid duplicate work at next run
	Move-Item "C:\script\test\ftp\MAC*" "C:\script\test\ftp\archive"



#find all MAH files in the immediate directory
dir "C:\script\test\ftp\MAH*.zip" | foreach-object{

	$id = $_.Name.SubString(4,8); 
    if(-not (Test-Path c:\script\test\Master\MAH\$id)) {mkdir c:\script\test\Master\MAH\$id}; 

	extract-zip $_ c:\script\test\Master\MAH\

    if(-not (Test-Path c:\script\test\Working\MAH\$id)) {mkdir c:\script\test\Working\MAH\$id}; 

	extract-zip $_ c:\script\test\Working\MAH\

}

Move-Item "C:\script\test\ftp\MAH*" "C:\script\test\ftp\archive"




#find all MAM files in the immediate directory
dir "C:\script\test\ftp\MAM*.zip" | foreach-object{

	$id = $_.Name.SubString(4,8); 
    if(-not (Test-Path c:\script\test\Master\MAM\$id)) {mkdir c:\script\test\Master\MAM\$id}; 

	extract-zip $_ c:\script\test\Master\MAM\$id

    if(-not (Test-Path c:\script\test\Working\MAM\$id)) {mkdir c:\script\test\Working\MAM\$id}; 

	extract-zip $_ c:\script\test\Working\MAM\$id

}

# Move-Item "C:\script\test\ftp\MAM*" "C:\script\test\ftp\archive"

Open in new window


you got me to thinking about the destination etc. so i started looking at some of the attempts early on in this project when i was trying to create the file structure then populate with 7zip command line interface... an attempt that failed miserably, and the light bulb went off.

Author

Commented:
Do you know of a way to ensure a silent copy? ie overwrite existing file if copying a newer one?

this will be run through scheduler, so I must avoid a need for interaction.
Full stack Software Engineer
Commented:
ok, first of all i hope you use PowerGUI to debug your script cause it's the best IDE for PowerShell which allows u to debug and got intellisense and some other cool stuff (and its open source).
http://powergui.org/downloads.jspa

here's the script, that should do the job (although i can make it even shorter):

function Extract-Zip
{
	param([string]$zipfilename, [string] $destination)

	if(test-path($zipfilename))
	{	
		$shellApplication = new-object -com shell.application
		$zipPackage = $shellApplication.NameSpace($zipfilename)
		$destinationFolder = $shellApplication.NameSpace($destination)
		$destinationFolder.CopyHere($zipPackage.Items())
	}
}

$MACFolders = "c:\script\test\Master\MAC", "c:\script\test\Working\MAC"
$MAHFolders = "c:\script\test\Master\MAH", "c:\script\test\Working\MAH"
$MAMFolders = "c:\script\test\Master\MAM", "c:\script\test\Working\MAM"

#find all MAC files in the immediate directory
dir "C:\script\test\ftp\MAC*.zip" | foreach-object{
	$filename = $_.BaseName
	$srcfile = $_
	
	$MACFolders | foreach {
		$newdest = Join-Path $_ $filename
	     if(-not (Test-Path ($newdest))){
			#create directory
			md $newdest
		}
		
		#extract	
		extract-zip $srcfile $newdest
	}
}

#clean up to avoid duplicate work at next run
Move-Item "C:\script\test\ftp\MAC*" "C:\script\test\ftp\archive"


#find all MAH files in the immediate directory
dir "C:\script\test\ftp\MAH*.zip" | foreach-object{

	$filename = $_.BaseName
	$srcfile = $_
	
	$MAHFolders | foreach {
		$newdest = Join-Path $_ $filename
	     if(-not (Test-Path ($newdest))){
			#create directory
			md $newdest
		}
		
		#extract	
		extract-zip $srcfile $newdest
	}
}

#clean up to avoid duplicate work at next run
Move-Item "C:\script\test\ftp\MAH*" "C:\script\test\ftp\archive"

#find all MAM files in the immediate directory
dir "C:\script\test\ftp\MAM*.zip" | foreach-object{

	$filename = $_.BaseName
	$srcfile = $_
	
	$MAMFolders | foreach {
		$newdest = Join-Path $_ $filename
	     if(-not (Test-Path ($newdest))){
			#create directory
			md $newdest
		}
		
		#extract	
		extract-zip $srcfile $newdest
	}
}

# Move-Item "C:\script\test\ftp\MAM*" "C:\script\test\ftp\archive"
                                            

Open in new window

Author

Commented:
Thank you so much, and as I'm not using anything but running in powershell to "debug" i will certainly use your suggestion moving forward.

This is so much cleaner!! VERY much appreciated!
Meir RivkinFull stack Software Engineer

Commented:
here's the script as optimal as possible:

function Extract-Zip
{
	param([string]$zipfilename, [string] $destination)

	if(test-path($zipfilename))
	{	
		$shellApplication = new-object -com shell.application
		$zipPackage = $shellApplication.NameSpace($zipfilename)
		$destinationFolder = $shellApplication.NameSpace($destination)
		$destinationFolder.CopyHere($zipPackage.Items())
	}
}

cls

$archivefolder = "C:\script\test\ftp\archive"

if(-not (Test-Path $archivefolder)){
	md $archivefolder 
}
		
$postfixes = "MAC", "MAH", "MAM"

$postfixes | foreach {
	$postfix = $_
	$masterfolder = Join-Path "c:\script\test\Master\" $postfix
	$workingfolder = Join-Path "c:\script\test\Working\" $postfix
	$dirfolder = "C:\script\test\ftp\$postfix*.zip"
	dir $dirfolder | foreach-object{
		$filename = $_.BaseName
		$srcfile = $_
		
		$newdest = Join-Path $masterfolder $filename
		if(-not (Test-Path ($newdest))){
			#create directory
			md $newdest
		}
		
		extract-zip $srcfile $newdest
		
		$newdest = Join-Path $workingfolder $filename
		if(-not (Test-Path ($newdest))){
			#create directory
			md $newdest
		}
		
		extract-zip $srcfile $newdest 
		
		Move-Item $srcfile $archivefolder
	}
}

Open in new window

Author

Commented:
excellent,
and I assume that adding .SubString (4,8) to the end of
$newdest = Join-Path $masterfolder $filename
as in
$newdest = Join-Path $masterfolder $filename.SubString( 4,8)
works as well as
$newdest = Join-Path $_ $filename.SubString (4,8)
from your previous post?

and by the way, I wish I had been using PowerGUI the whole time, instead of notepad. Great tip.
Meir RivkinFull stack Software Engineer

Commented:
u don't need .SubString (4,8) cause $_.BaseName gives u only the file name without the extension.

so if the file is: c:\temp\jonny.zip
then  $_.BaseName equals jonny.
and that is what u wish to add to the $destination folder, and i use join-path to do so.

good luck.

Author

Commented:
In doing so i actually get a cleaner folder name.  For instance my zip file is actually quite long in name...
MAC.20120213-02.20.01
so i was using SubString (4,8) to extract only the yyyymmdd portion of the name so that the result would be <parent>\MAC\20120213 instead of <parent>\MAC\MAC.20120213-02.20.01

although both results are good enough

Author

Commented:
and thanks again!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial