Apologies, this question is scattered on the internet but I have yet to find a satisfactory answer that uses only Powershell 2.0 (with .NET v3.5) - no external libraries or programs
I'm using the following code to extract log.txt from ZipFile.zip (no matter log.txt's location)
$Destination = (new-object -com shell.application).NameSpace('C:\ZipExtractDir')
$ZipFile = (new-object -com shell.application).NameSpace('C:\ZipFile.zip')
$Destination.CopyHere(($Zipfile.Items() | where-object {$_.Name -like '*log.txt'}), 1044)
Works if log.txt is in directory root \log.txt
Fails if log.txt is in a subdirectory \Subfolder\log.txt
Fails if referencing the literal (.zip) path
{$_.Name -Like '*Subfolder\log.txt'} (both double & single quotes fail)
Have tried using -eq -like -contains '' "" $_.FullName
I'm quite certain that I'm filtering incorrectly - can anyone help with this code so that it will parse subdirectories as well?
Similar to what you have already done, you can set up the Shell.Application namespaces like this. Then you can copy the extracted directory to the destination path.
$zipFilePath = "Zipfile.zip"
$destinationPath = "C:\Users\Public\Downloads"
$zipfile = (New-Object -Com Shell.Application).NameSpace($zipFilePath)
$destination = (New-Object -Com Shell.Application).NameSpace($destinationPath)
$destination.CopyHere($zipfile.Items())
Then to list the log.txt files, we can contruct the full extracted path with Join-Path. This basically just appends the zip file name from System.IO.Path.GetFileNameWithoutExtension() to the destination path. Then just use Get-ChildItem to list the files recursively with the -Recurse and -Filter switches.
$extractedPath = Join-Path -Path $destinationPath -ChildPath ([System.IO.Path]::GetFileNameWithoutExtension($zipFilePath))
Get-ChildItem -Path $extractedPath -Filter log.txt -Recurse
And to test this for PowerShell 2.0 we can use -version 2 with powershell.exe:
powershell.exe -version 2 .\test.ps1
UPDATE
If you want to inspect files before extracting, you'll need to recurse the directories yourself. Below is a demo of how this can be done.
function New-ZipChildRootFolder
{
param
(
[string]$DestinationPath,
[string]$ZipFileName
)
$folderPath = Split-Path -Path $ZipFileName -Leaf
$destination = (New-Object -ComObject Shell.Application).NameSpace($DestinationPath)
$destination.NewFolder($folderPath)
}
function Get-ZipChildItems
{
param
(
[string]$ZipFilePath,
[string]$DestinationPath
)
$zipfile = (New-Object -ComObject Shell.Application).NameSpace($ZipFilePath)
$zipFileName = [System.IO.Path]::GetFileNameWithoutExtension($ZipFilePath)
Write-Output "Create root zip folder : $zipFileName"
New-ZipChildRootFolder -DestinationPath $DestinationPath -ZipFileName $zipFileName
foreach ($item in $zipFile.items())
{
Get-ZipChildItemsRecurse -Items $item -DestinationPath $DestinationPath -ZipFileName $zipFileName
}
}
function Get-ZipChildItemsRecurse
{
param
(
[object]$Items,
[string]$DestinationPath,
[string]$ZipFileName
)
foreach ($file in $Items.getFolder.Items())
{
if ($file.IsFolder -eq $true)
{
Write-Output "Creating folder : $($file.Path)"
New-ZipChildFolder -Folder $file -DestinationPath $DestinationPath -ZipFileName $ZipFileName
Get-ZipChildItemsRecurse -Items $file -DestinationPath $DestinationPath -ZipFileName $ZipFileName
}
else
{
$filename = Split-Path -Path $file.Path -Leaf
if ($filename -eq "log.txt")
{
Write-Output "Copying file : $($file.Path)"
New-ZipChildFile -File $file -DestinationPath $DestinationPath -ZipFileName $ZipFileName
}
}
}
}
function New-ZipChildFile
{
param
(
[object]$File,
[string]$DestinationPath,
[string]$ZipFileName
)
$destination = New-Object -ComObject Shell.Application
$items = $File.Path.Split("\")
$zipRootIndex = [array]::IndexOf($items, $ZipFileName)
$path = $items[$zipRootIndex..($items.Length - 2)] -join "\"
$fullPath = Join-path -Path $DestinationPath -ChildPath $path
$destination.NameSpace($fullPath).CopyHere($File)
}
function New-ZipChildFolder
{
param
(
[object]$Folder,
[string]$DestinationPath,
[string]$ZipFileName
)
$destination = New-Object -ComObject Shell.Application
$items = $Folder.Path.Split("\")
$zipRootIndex = [array]::IndexOf($items, $ZipFileName)
$folders = $items[$zipRootIndex..($items.Length - 1)]
$currentFolder = $DestinationPath
foreach ($folder in $folders)
{
$destination.NameSpace($currentFolder).NewFolder($folder)
$currentFolder = Join-Path -Path $currentFolder -ChildPath $folder
}
}
Usage:
$zipFilePath = "C:\Zipfile.zip"
$destinationPath = "C:\Users\Public\Downloads"
Get-ZipChildItems -ZipFile $zipFilePath -DestinationPath $destinationPath
Related
I am trying to build a script that can do the following
When I open the powershell script
it should ask me for a path where the files are located e.g.
C:\SearchPath\DateOfToday
Daily there will be a new folder e.g. 20221214.
Maybe it is possible to specify the path and the script will get today's date by itself or maybe just opening a windows explorer so I can select the SourceFolder.
A file must be copied e.g. Test.xmr and then renamed to Test.$xmr
some more files should be collected like
jpl, eml, html, pdf, cml, xmr, $xmr and xml
the collected files should be moved to the folder
C:\AnotherFolder\
What I have so far is this
$SourceFolder = "\\Path1\Dateoftoday"
$DestFolder = '\\Path2' #Path to Destination Folder
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.xmr"
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.jpl"
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.eml"
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.html"
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.pdf"
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.cml"
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "ABC.xml"
ForEach ($F in $FileList) {
Copy-Item $F.FullName (Join-Path $DestFolder ($F.Name))
}
I can specify the path and the filenames and then run it.
Sorry if it's basic, but I'm new at powershell.
Looks like you just need this:
$today = '{0:yyyyMMdd}' -f (Get-Date)
$SourceFolder = "\\Path1\$today"
$DestFolder = '\\Path2' #Path to Destination Folder
$filePattern = '*.xmr', '*.jpl', '*.eml', '*.html', '*.pdf', '*.cml', '*.xml', '*.$xmr'
# unclear about your question 2)..
# do we need to copy a file to the $SourceFolder first ??
# Copy-Item -Path 'X:\Somewhere\Test.xmr' -Destination (Join-Path -Path $SourceFolder -ChildPath 'Test.$xmr')
# copy all chosen files to the destination folder
Get-ChildItem -LiteralPath $SourceFolder -Recurse -File -Include $filePattern | ForEach-Object {
$_ | Copy-Item -Destination $DestFolder
}
As per your comment, hopefully this is what you need:
$today = '{0:yyyyMMdd}' -f (Get-Date)
$SourceFolder = "\\Path1\$today"
$DestFolder = '\\Path2' #Path to Destination Folder
$extensions = 'xmr', 'jpl', 'eml', 'html', 'pdf', 'cml', 'xml'
# ask for the filename
$baseName = Read-Host 'Please enter the file name without extension'
if (![string]::IsNullOrWhiteSpace($baseName)) {
# construct the filenames to copy
$pattern = $extensions | ForEach-Object { '{0}.{1}' -f $baseName.Trim(), $_ }
# copy all chosen files to the destination folder
Get-ChildItem -LiteralPath $SourceFolder -Recurse -File |
Where-Object { $pattern -contains $_.Name } | ForEach-Object {
# rename the extensions by prefixing it with a dollar sign ($)
$targetFile = Join-Path -Path $DestFolder -ChildPath ('{0}.${1}' -f $_.BaseName, $_.Extension.TrimStart("."))
$_ | Copy-Item -Destination $targetFile
# if you want ONLY the .xmr file to be renamed with as .$xmr use this instead:
# if ($_.Extension -eq '.xmr') {
# $targetFile = Join-Path -Path $DestFolder -ChildPath ('{0}.${1}' -f $_.BaseName, $_.Extension.TrimStart("."))
# $_ | Copy-Item -Destination $targetFile
# }
# else {
# $_ | Copy-Item -Destination $DestFolder
# }
}
}
Edit
As you commentd you want to remove the leftover .xmr and .att files after copying, here the code that does that too
$today = '{0:yyyyMMdd}' -f (Get-Date)
$SourceFolder = "\\Path1\$today"
$DestFolder = '\\Path2' #Path to Destination Folder
$extensions = 'xmr', 'jpl', 'eml', 'html', 'pdf', 'cml', 'xml', 'att'
# ask for the filename
$baseName = Read-Host 'Please enter the file name without extension'
if (![string]::IsNullOrWhiteSpace($baseName)) {
# construct the filenames to copy
$pattern = $extensions | ForEach-Object { '{0}.{1}' -f $baseName.Trim(), $_ }
# copy all chosen files to the destination folder
(Get-ChildItem -LiteralPath $SourceFolder -Recurse -File) |
Where-Object { $pattern -contains $_.Name } | ForEach-Object {
# rename the extensions by prefixing it with a dollar sign ($)
$targetFile = Join-Path -Path $DestFolder -ChildPath ('{0}.${1}' -f $_.BaseName, $_.Extension.TrimStart("."))
$_ | Copy-Item -Destination $targetFile
# if you want ONLY the .xmr file to be renamed with as .$xmr use this instead:
# if ($_.Extension -eq '.xmr') {
# $targetFile = Join-Path -Path $DestFolder -ChildPath ('{0}.${1}' -f $_.BaseName, $_.Extension.TrimStart("."))
# $_ | Copy-Item -Destination $targetFile
# }
# else {
# $_ | Copy-Item -Destination $DestFolder
# }
# remove leftover .XMR and .ATT files
if ($_.Extension -match '\.(xmr|att)$') { $_ | Remove-Item -Force }
}
}
Good day!!
Maybe we can help you with this:
First of all you need to ask for the path and we have two ways to do this (maybe more) but those are the ways I know
The first option is the simple, using the Read-Host Cmdlet:
$path = Read-Host "Please type the path of the folder"
The second option is more complicated and use dotnet framework: (to ask for the destination just change the last two variables with destination instead of sourcePath)
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$box = New-Object System.Windows.Forms.Form
$box.Text = 'Script Execution'
$box.Size = New-Object System.Drawing.Size(300,200)
$box.StartPosition = 'CenterScreen'
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(75,120)
$okButton.Size = New-Object System.Drawing.Size(75,23)
$okButton.Text = 'OK'
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$box.AcceptButton = $okButton
$box.Controls.Add($okButton)
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Point(150,120)
$cancelButton.Size = New-Object System.Drawing.Size(75,23)
$cancelButton.Text = 'Cancel'
$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$box.CancelButton = $cancelButton
$box.Controls.Add($cancelButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = 'Please type the path of the files:'
$box.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$box.Controls.Add($textBox)
$box.Topmost = $true
$box.Add_Shown({$textBox.Select()})
$result = $box.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$sourcePath = $textBox.Text
$sourcePath
}
Once we have the path we can copy the files to do it we can use Copy-item
(I'm not sure if you want to rename the original files or the final files, anyway if you want to rename the last files just use rename-item cmdlet)
$fileList = #(
Get-ChildItem -Path $sourcePath -Recurse -include "*.jpl", "*.eml", "*.html", "*.pdf", "*.cml", "*.xml"
)
foreach ($f in $fileList){
Copy-Item -Path $f.fullname -Destination "$destPath\$f.name"
}
And this should be enough, i'm not really sure about the $fileList part maybe I made some typo, sorry in this case.
Have a nice day!
I am trying to copy all files in folders and sub-folders not older than 300 minutes, but the code I got working only copies the files in the main folder, it doesn't copy the files in subfolders.
At the destination I don't want to maintain the folder structure of the original files, I just want to put all the origin files into a single specific destination folder.
This is the code I have:
Powershell -NoL -NoP -C "&{$ts=New-TimeSpan -M 300;"^
"Get-ChildItem "C:\Origin" -Filter '*.dat'|?{"^
"$_.LastWriteTime -gt ((Get-Date)-$ts)}|"^
%%{Copy-Item $_.FullName 'C:\Destination'}}"
Could someone help me out please?
Thanks in advance.
Here's a modified script for you you can save as "Copy-Unique.ps1" you can run from a batch file.
function Copy-Unique {
# Copies files to a destination. If a file with the same name already exists in the destination,
# the function will create a unique filename by appending '(x)' after the name, but before the extension.
# The 'x' is a numeric sequence value.
[CmdletBinding(SupportsShouldProcess)] # add support for -WhatIf switch
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[Alias("Path")]
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[string]$SourceFolder,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DestinationFolder,
[Parameter(Mandatory = $false)]
[int]$NewerThanMinutes = -1,
[Parameter(Mandatory = $false)]
[string]$Filter = '*',
[switch]$Recurse
)
# create the destination path if it does not exist
if (!(Test-Path -Path $DestinationFolder -PathType Container)) {
Write-Verbose "Creating folder '$DestinationFolder'"
$null = New-Item -Path $DestinationFolder -ItemType 'Directory' -Force
}
# get a list of file FullNames in this source folder
$sourceFiles = #(Get-ChildItem -Path $SourceFolder -Filter $Filter -File -Recurse:$Recurse)
# if you want only files not older than x minutes, apply an extra filter
if ($NewerThanMinutes -gt 0) {
$sourceFiles = #($sourceFiles | Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-$NewerThanMinutes) })
}
foreach ($file in $sourceFiles) {
# get an array of all filenames (names only) of the files with a similar name already present in the destination folder
$destFiles = #((Get-ChildItem $DestinationFolder -File -Filter "$($file.BaseName)*$($file.Extension)").Name)
# for PowerShell version < 3.0 use this
# $destFiles = #(Get-ChildItem $DestinationFolder -Filter "$baseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename
$newName = $file.Name
$count = 1
while ($destFiles -contains $newName) {
$newName = "{0}({1}){2}" -f $file.BaseName, $count++, $file.Extension
}
# use Join-Path to create a FullName for the file
$newFile = Join-Path -Path $DestinationFolder -ChildPath $newName
Write-Verbose "Copying '$($file.FullName)' as '$newFile'"
$file | Copy-Item -Destination $newFile -Force
}
}
# you can change the folder paths, file pattern to filter etc. here
$destFolder = Join-Path -Path 'C:\Destination' -ChildPath ('{0:yyyy-MM-dd_HH-mm}' -f (Get-Date))
Copy-Unique -SourceFolder "C:\Origin" -DestinationFolder $destFolder -Filter '*.dat' -Recurse -NewerThanMinutes 300
Changed the code to now take a datetime object to compare against rather than an amount of minutes. This perhaps makes the code easier to understand, but certainly more flexible.
function Copy-Unique {
# Copies files to a destination. If a file with the same name already exists in the destination,
# the function will create a unique filename by appending '(x)' after the name, but before the extension.
# The 'x' is a numeric sequence value.
[CmdletBinding(SupportsShouldProcess)] # add support for -WhatIf switch
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[Alias("Path")]
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[string]$SourceFolder,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DestinationFolder,
[string]$Filter = '*',
[datetime]$NewerThan = [datetime]::MinValue,
[switch]$Recurse
)
# create the destination path if it does not exist
if (!(Test-Path -Path $DestinationFolder -PathType Container)) {
Write-Verbose "Creating folder '$DestinationFolder'"
$null = New-Item -Path $DestinationFolder -ItemType 'Directory' -Force
}
# get a list of file FullNames in this source folder
$sourceFiles = #(Get-ChildItem -Path $SourceFolder -Filter $Filter -File -Recurse:$Recurse)
# if you want only files newer than a certain date, apply an extra filter
if ($NewerThan -gt [datetime]::MinValue) {
$sourceFiles = #($sourceFiles | Where-Object { $_.LastWriteTime -gt $NewerThan })
}
foreach ($file in $sourceFiles) {
# get an array of all filenames (names only) of the files with a similar name already present in the destination folder
$destFiles = #((Get-ChildItem $DestinationFolder -File -Filter "$($file.BaseName)*$($file.Extension)").Name)
# for PowerShell version < 3.0 use this
# $destFiles = #(Get-ChildItem $DestinationFolder -Filter "$baseName*$extension" | Where-Object { !($_.PSIsContainer) } | Select-Object -ExpandProperty Name)
# construct the new filename
$newName = $file.Name
$count = 1
while ($destFiles -contains $newName) {
$newName = "{0}({1}){2}" -f $file.BaseName, $count++, $file.Extension
}
# use Join-Path to create a FullName for the file
$newFile = Join-Path -Path $DestinationFolder -ChildPath $newName
Write-Verbose "Copying '$($file.FullName)' as '$newFile'"
$file | Copy-Item -Destination $newFile -Force
}
}
# you can change the folder paths, file pattern to filter etc. here
$destFolder = Join-Path -Path 'D:\Destination' -ChildPath ('{0:yyyy-MM-dd_HH-mm}' -f (Get-Date))
Copy-Unique -SourceFolder "C:\Origin" -DestinationFolder $destFolder -Filter '*.dat' -Recurse -NewerThan (Get-Date).AddMinutes(-300)
When you have saved the above code to let's say 'C:\Scripts\Copy-Unique.ps1' you can then call it from a batch file like:
Powershell.exe -NoLogo -NoProfile -File "C:\Scripts\Copy-Unique.ps1"
I'm currently writing a simple PowerShell script.
Basically, it should get the list of servers from a notepad and start to unzip the .zip file on each server and extract it to the new folder.
However, the script is not extracting all files under the zip file.
It would only extract one file from it and I'm not sure why the foreach loop not working properly.
Please shed some light on this issue. Thanks.
$servers = Get-Content "C:\tmp\script\new_unzip\servers.txt"
$Date = ((Get-Date).ToString('dd-MM-yyyy_HH-mm-ss'))
foreach ($server in $servers) {
$shell = new-object -com shell.application
$target_path = "\\$server\c$\Temp\FFPLUS_Temp"
$location = $shell.namespace($target_path)
$ZipFiles = Get-ChildItem -Path $target_path -Filter *.zip
$ZipFiles | Unblock-File
foreach ($ZipFile in $ZipFiles) {
$ZipFile.fullname | out-default
$NewLocation = "\\$server\c$\Temp\FFPLUS_Temp\$Date"
New-Item $NewLocation -type Directory -Force -ErrorAction SilentlyContinue
Move-Item $ZipFile.fullname $NewLocation -Force -ErrorAction SilentlyContinue
$NewZipFile = Get-ChildItem $NewLocation *.zip
$NewLocation = $shell.namespace($NewLocation)
$ZipFolder = $shell.namespace($NewZipFile.fullname)
$NewLocation.copyhere($ZipFolder.items())
}
}
$servers = Get-Content "C:\tmp\script\updated\servers.txt"
$Date = ((Get-Date).ToString('dd-MM-yyyy_HH-mm-ss'))
foreach ($server in $servers)
{
$zipFolder = "\\$server\c$\Temp\FFPLUS_Temp"
Add-Type -assembly System.IO.Compression.Filesystem
$zipFiles = Get-ChildItem -Path $zipFolder -Filter *.zip
foreach($zip in $zipFiles)
{
$destPath = "\\$server\c$\Temp\FFPLUS_Temp\$Date"
New-Item -ItemType Directory $destPath
[io.compression.zipfile]::ExtractToDirectory([string]$zip.FullName, "$destPath")
Move-Item $zip.fullname $destPath -Force -ErrorAction SilentlyContinue
}
}
I have a PowerShell script that moves all files from one location to another that have a date modified older than 3 years. I have it so the file when moved to the new location also keeps the file structure of the original.
I am trying to make it so once the file has been moved to the new location it creates a shortcut in the original directory which points to the new location of the file.
Below is my script so far which does all the above minus the shortcut.
$sourceDir = "C:\Users\bgough\Documents\powershell\docs"
$archiveTarget = "C:\Users\bgough\Documents\archive"
$dateToday = Get-Date
$date = $dateToday.AddYears(-3)
$items = Get-ChildItem $sourceDir -Recurse |
Where-Object {!$_.PSIsContainer -and $_.LastWriteTime -le $date}
foreach ($item in $items)
{
$withoutRoot = $item.FullName.Substring([System.IO.Path]::GetPathRoot($item.FullName).Length);
$destination = Join-Path -Path $archiveTarget -ChildPath $withoutRoot
$dir = Split-Path $destination
if (!(Test-Path $dir))
{
mkdir $dir
}
Move-Item -Path $item.FullName -Destination $destination
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$sourceDir")
$Shortcut.TargetPath = $destination
$Shortcut.Save()
}
In my script I have included my attempt at creating this shortcut but it hasn't helped. I have also read through the following but don't understand it too well..
How to create a shortcut using Powershell
Powershell Hard and Soft Links
Edit:
I have successfully got the shortcut to create and in the original folder. However, I can't seem to figure out how to pass a variable to use as the shortcut name. At the moment a string is hard coded, which is what the shortcut gets named. Please see code below: I would like to set the name as the item full name (Same name as document that was moved).
$sourceDir = "C:\Users\bgough\Documents\powershell\docs"
$archiveTarget = "C:\Users\bgough\Documents\archive"
$dateToday = Get-Date
$date = $dateToday.AddYears(-3)
$items = Get-ChildItem $sourceDir -recurse | Where-Object {!$_.PsIsContainer -and $_.LastWriteTime -le $date}
foreach ($item in $items)
{
$withoutRoot = $item.FullName.Substring([System.IO.Path]::GetPathRoot($item.FullName).Length);
$destination = Join-Path -Path $archiveTarget -ChildPath $withoutRoot
$dir = Split-Path $destination
if (!(Test-Path $dir))
{
mkdir $dir
}
Move-Item -Path $item.FullName -Destination $destination
$wshshell = New-Object -ComObject WScript.Shell
$desktop = [System.Environment]::GetFolderPath('Desktop')
$lnk = $wshshell.CreateShortcut($sourceDir + "\ShortcutName.lnk")
$lnk.TargetPath = "$destination"
$lnk.Save()
}
.lnk files are fine when you're using Explorer but they don't play well in Powershell or a command prompt.
What you need to do is create a symbolic link for the file. You can't do this in Powershell, but there is a command line utility called mklink that does it. I've wrapped it in a function so that you can call it:
function CreateLink
{
param
(
[string] $LinkName,
[string] $TargetFile
)
&"cmd.exe" /c mklink "$LinkName" "$TargetFile" | Out-Null
}
In your example you would call it like this:
CreateLink -LinkName $item.FullName -TargetFile $destination
When you look at the directory in Powershell the file will show up as being 0 bytes in size. Don't worry about that.
Thanks for your script Android Magic.
I have modified it to:
Copy a set of files from source to destination
It creates the identical folder structure on the destination, even if the folders are empty
It then creates a symbolic link to the archived file. SymbolicLink support was added in Powershell v5.1. You have to run the script as Admin in order for the Symbolic Link creation to work.
I'd like to add a function to email if anything goes wrong and a summary of status, but that's for another day.
$sourceDir = "\\Fileserver1\IT\Vendor"
$archiveTarget = "\\FS-ARCHIVE\Archive\Fileserver1\IT\Vendor"
$rootArchivePath = "\\FS-ARCHIVE\Archive"
$dateToday = Get-Date
$date = $dateToday.AddYears(-3)
# Copy folder structure to Archive
Get-ChildItem -Path $sourceDir -Recurse |
?{ $_.PSIsContainer } |
Copy-Item -Destination {Join-Path $archiveTarget $_.Parent.FullName.Substring($sourceDir.length)} -Force
$items = Get-ChildItem $sourceDir -Recurse -Attributes !Directory |
Where-Object {$_.LastAccessTime -le $date}
foreach ($item in $items)
{
$withoutRoot = Split-Path -Path $item.FullName
$destination = $rootArchivePath + $withoutRoot.Remove(0,1)
$destFile = $destination + "\" + $item
Move-Item -Force -Path $item.FullName -Destination $destination -Verbose
New-Item -ItemType SymbolicLink -Path $withoutRoot -Name $item -Value $destFile -Force -Verbose
}
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