automated folder structure creation with conditions - powershell

any idea / suggestion how to simplify this code? I would like to find different approach to create folders as they need to be created.
function TargetDir { # create folder structure for patches by OS type
if ($col6 -like "*WindowsXP*" -or $col4 -like "*Windows XP*"
) {$TargetDir = "$Path\$col1\XP"
if (!(Test-Path -path $TargetDir
)) {New-Item $Path\$col1\XP -type directory
New-Item -type file -Name $Col6 -Path $TargetDir}
else {New-Item -type file -Name $Col6 -Path $TargetDir}
}
elseif ($col6 -like "*Windows6.0*" -or $col4 -like "*Vista*"
) {$TargetDir = "$Path\$col1\Vista"
if (!(Test-Path -path $TargetDir
) ) {New-Item $Path\$col1\Vista -type directory
New-Item -type file -Name $Col6 -Path $TargetDir}
else {New-Item -type file -Name $Col6 -Path $TargetDir}
}
elseif ($col6 -like "*Windows6.1*" -or $col4 -like "*Windows 7*"
) {$TargetDir = "$Path\$col1\Win7"
if (!(Test-Path -path $TargetDir
) ) {New-Item $Path\$col1\Win7 -type directory
New-Item -type file -Name $Col6 -Path $TargetDir}
else {New-Item -type file -Name $Col6 -Path $TargetDir}
}
elseif ($col6 -like "*Windows8-RT*" -or $col4 -like "*Windows 8 *"
) {$TargetDir = "$Path\$col1\Win8"
if (!(Test-Path -path $TargetDir)
) {New-Item $Path\$col1\Win8 -type directory
New-Item -type file -Name $Col6 -Path $TargetDir}
else {New-Item -type file -Name $Col6 -Path $TargetDir}
}
elseif ($col6 -like "*Windows8.1*" -or $col4 -like "*Windows 8.1*"
) {$TargetDir = "$Path\$col1\Win81"
if (! (Test-Path -path $TargetDir)
) {New-Item $Path\$col1\Win81 -type directory
New-Item -type file -Name $Col6 -Path $TargetDir}
else {New-Item -type file -Name $Col6 -Path $TargetDir}
}
} # end if
Thank you for any suggestion how this could be simplified.

When you find yourself using elseif a lot switch might be there to save you.
You are checking if $col4 or $col6 has a match correct? Since that appears the case you only really need to check one of those values as long as it is not blank (Just bear with me). Also to make your function more portable (if not for you then for others) then we are going to use function parameters.
function Get-TargetDirectory{
param(
[string]$value1,
[string]$value2,
[string]$path,
[string]$directory
)
If($value1){$toMatch = $value1} #The value of $value1 is tested to contain a non empty string / null
If($value2){$toMatch = $value2} #The value of $value2 is tested to contain a non empty string / null
switch -Regex ($toMatch){
"Windows ?XP"{$TargetDir = "$Path\$directory\XP"}
"(Windows6\.0|Vista)"{$TargetDir = "$Path\$directory\Vista"}
"(Windows6\.1|Windows 7)"{$TargetDir = "$Path\$directory\Win7"}
"Windows ?8"{$TargetDir = "$Path\$directory\Win8"}
"Windows ?8\.1"{$TargetDir = "$Path\$directory\Win81"}
default{$TargetDir = ""}
}
If($TargetDir){ #The value of $TargetDir is tested to contain a non empty string / null
# Create the directory if it does not already exist
If(!(Test-Path -path $TargetDir)){[void](New-Item $TargetDir -type Directory)}
# Create the empty file in $TargetDir
New-Item -type file -Name $value1 -Path $TargetDir
}
}
I changed the names in my function to be more friendly. The following is how they map to your previous names.
Yours Get-TargetDirectory
$col1 $directory
$col4 $value2
$col6 $value1
$path $path
Not perfect as there are some caveats that you dont already account for like the presence of the folder I refer to as $directory. Using the -Regex keyword we can simplify the match operations. Also, there is no need to have the code to create items exist multiple times since it will be called regardless.
Hopefully this, untested code, works for you and shows you some good coding practices.

Matt thank you for your replay. I admit that I shared almost non detail but I had on my mind something much more simpler... Your suggestion to use switch was the key to get it done that way.
switch -wildcard ($FileName)
{
"*6.0*" { $TargetDir = "$Path\Vista\" }
"*6.1*" { $TargetDir = "$Path\Windows 7\" }
"*8-R*" { $TargetDir = "$Path\Windows 8\" }
"*8.1*" { $TargetDir = "$Path\Windows 8.1\" }
default { write-host "Define target directory for $FileName" -foreground "red"}
}
if (Test-Path -path $TargetDir) {
New-Item $TargetDir -type directory
New-Item -type file -Name $FileName -Path $TargetDir
} else {
New-Item -type file -Name $FileName -Path $TargetDir -Force
}

Related

How to write all the files names from a folder inside a txt file

With this code I get the data but is not reflected on the txt file. I just get a bunch of Microsoft.PowerShell.Commands.Internal.Format.FormatStartData.
Microsoft.PowerShell.Commands.Internal.Format.GroupStartData
Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData
Instead the names of each file.
Here is the code:
param(
[string]$foldername = 'unknown'
)
if (Test-Path $foldername){
$location = Join-Path -Path $HOME -ChildPath "files.txt"
$files = Get-ChildItem $foldername | Where-Object {$_.PsIsContainer -ne $True} | Format-Table
Name
$amount = Get-ChildItem $foldername | Where-Object {$_.PsIsContainer -ne $True} | Measure-
Object
Add-Content -Path $location -Value $files
Write-Host $amount.count "filenames were written to file" $location
}
else {Write-Host "Sorry,", $foldername, "does not exist."
break
}
First get-childitem supports the switch File, no need to pipe and filter. The Format-Table is used to format the output displayed on the screen, so as you want to write to a file you do not need it.
Also only use break within a loop, if you want to stop the processing use return instead.
param(
[string]$foldername = 'unknown'
)
if (Test-Path $foldername){
$location = Join-Path -Path $HOME -ChildPath "files.txt"
#gets all files in the specified path
$files = Get-ChildItem $foldername -File
#counts the number of files
$amount = $files.count
#If you want the fullpath in the logfile use $files.fullname if you only want the name use $files.name
$files.FullName | Add-Content -Path $location
Write-Host "$amount filenames were written to file $location"
}
else {
Write-Host "Sorry, $foldername, does not exist."
return
}

Powershell 2.0 extract certain files from zip (include subdirectories)

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

Powershell script to create a single folder based of the 5 digits in the name of the pdf's

So far I have tried the following script:
$SourceFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\"
$TargetFolder = "D:\WORK\JetLetter\LKY\LKY_jV_004\Final\"
Get-ChildItem -Path $SourceFolder -Filter *.pdf |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.pdf','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
if( -not ( Test-Path -Path $Destination.Directory.FullName )){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
This creates a folder for every pdf in the folder.
I need it create a single folder based on the 5 digit in the name and move those files into the new folder.
For example: I could have 10 pdf's that have the number "30565" in them and the new folder should be named "30565"
Here are some file names to explain:
LKY_20974_Pr01_1-5000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr02_5001-10000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
LKY_20974_Pr03_10001-15000.pdf
to
D:\WORK\JetLetter\LKY\LKY_jV_004\Final\20974
I have tried to include an else block to the best answer script and haven't had much success. I did however create a separate script that will archive the files before creating a new file. I just have to run it before the main powershell script.
$SourceDir = 'D:\WORK\JetLetter\LKY\LKY_jV_004_9835'
$DestDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files'
$ArchiveDir = 'D:\WORK\JetLetter\LKY\#Print_Production_Files\#archive'
$Filter = '*.pdf'
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (Test-Path -LiteralPath $FullTargetDir)
{
# the "$Null =" is to suppress unwanted output about what was done
$null = Move-Item -Path $FullTargetDir -Destination $ArchiveDir -Force
}
}
This has made the files and folders a lot more organized.
i think this does what you want done. [grin] the comments seem adequate, but if you have any questions, please ask.
$SourceDir = 'c:\temp\JetLetter\LKY\LKY_jv_004'
$DestDir = 'c:\temp\JetLetter\LKY\LKY_jv_004\Final'
$Filter = '*.pdf'
#region >>> make the dirs and sample files to work with
# remove the entire "#region/#endregion" block when you are ready to work with real data
# make the dirs
$Null = mkdir -Path $SourceDir, $DestDir -ErrorAction 'SilentlyContinue'
# make the test files
$SampleFiles = #(
'LKY_11111_Pr11_1-11111.pdf'
'LKY_22222_Pr22_2-22222.pdf'
'LKY_22222_Pr22_2222-2222.pdf'
'LKY_33333_Pr33_3-3333.pdf'
'LKY_33333_Pr33_33333-33333.pdf'
'LKY_55555_Pr55_5-5555.pdf'
'LKY_77777_Pr77_7-77777.pdf'
'LKY_77777_Pr77_77777-77777.pdf'
'LKY_99999_Pr99_9-99999.pdf'
)
foreach ($SF_Item in $SampleFiles)
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $SourceDir -Name $SF_Item -ItemType 'File' -ErrorAction 'SilentlyContinue'
}
#endregion >>> make the dirs and sample files to work with
$FileList = Get-ChildItem -LiteralPath $SourceDir -Filter $Filter -File
foreach ($FL_Item in $FileList)
{
# this presumes the target dir number is ALWAYS the 2nd item in the split string
$TargetDir = $FL_Item.BaseName.Split('_')[1]
$FullTargetDir = Join-Path -Path $DestDir -ChildPath $TargetDir
if (-not (Test-Path -LiteralPath $FullTargetDir))
{
# the "$Null =" is to suppress unwanted output about what was done
$Null = New-Item -Path $FullTargetDir -ItemType 'Directory'
}
$NewFullFileName = Join-Path -Path $FullTargetDir -ChildPath $FL_Item.Name
# leave the file in the source dir if it already is in the final target dir
# you may want to save the not-copied info to a file for later review
if (-not (Test-Path -LiteralPath $NewFullFileName))
{
# the "Move-Item" cmdlet on win7ps5.1 is wildly unreliable
# so i used copy & then remove
$Null = Copy-Item -LiteralPath $FL_Item.FullName -Destination $NewFullFileName
Remove-Item -LiteralPath $FL_Item.FullName
}
else
{
Write-Warning (' {0} already exists in {1}' -f $FL_Item.Name, $FullTargetDir)
Write-Warning ' The file was not moved.'
Write-Warning ''
}
}
screen output only exists for "not moved" files. again, you may want to save the list to a $Var or to a file for later work.
one of the moved files ...
C:\Temp\JetLetter\LKY\LKY_jv_004\Final\22222\LKY_22222_Pr22_2222-2222.pdf

Powershell Unzip One Specific Folder in a File with Dynamic Name

I found a piece of code here that almost does what I need, which is extract just one folder from an archived file.
The only issue I have is that the archive name changes month on month, therefore I wanted to use a wildcard. Once a wildcard is specified (* in $zipfile), the script does not work for me.
I would be grateful for any suggestions.
[Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null
$zipfile = 'C:\ALL\Debtor*.zip'
$folder = 'tmp\st\sd'
$dst = 'C:\ALL\ZipOutput'
[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)
Try this out for size. Just use Get-ChildItem to locate the zip file in your ALL directory.
[Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null
$zipfile = Get-ChildItem -Path C:\ALL\ -Filter *.zip | Where-Object {$_.Name -like "Debtor*"} | Select-Object -ExpandProperty FullName
$folder = 'tmp\st\sd\'
$dst = 'C:\ALL\ZipOutput'
[IO.Compression.ZipFile]::OpenRead($zipfile).Entries | Where-Object {
$_.FullName -like "$($folder -replace '\\','/')/*.*"
} | ForEach-Object {
$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)
}
I am also assuming that the archive name changes but there are not multiple archives with that name. If there are you will need to wrap everything in a Foreach($zip in $zipfile){ ... }

Moving files create shortcut in original location pointing to new location

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
}