How to print a specific part of directory path in powershell - powershell

My script is running in a loop on files and I display the path for each file but the path is too long and I want to display only the relevant directory. the path build always the same
examlpe for output path:
$File.DirectoryName
C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239\IDUDatabase_4.0.0.119\IDUDatabase
I am creating the temp folder and the sub folder as below:
$tempFolder = Join-Path ([IO.Path]::GetTempPath()) (New-GUID).ToString('n')
$subTemp = Join-Path -Path $tempFolder -ChildPath ([datetime]::Now.Ticks)
So this part is constant C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239
I want to display only the 'IDUDatabase_4.0.0.119\IDUDatabase'
The structure is always the same except the user name (C:\User\UserName)

You might use the Resolve-Path -Relative parameter for this:
Set-Location $subtemp
GCI $subtemp -Recurse |Foreach-Object { Resolve-Path -Relative $_.FullName }
.\IDUDatabase_4.0.0.119
.\New folder
.\IDUDatabase_4.0.0.119\IDUDatabase
.\New folder\New Text Document.txt

If you want to remove a prefix from a string you can use the Substring method…
$subTemp = "C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239"
$mypath = Join-Path -Path $subTemp -ChildPath "IDUDatabase_4.0.0.119\IDUDatabase"
$displayPath = $mypath.Substring($subTemp.Length+1)
$displayPath
# IDUDatabase_4.0.0.119/IDUDatabase
Note the +1 is there to remove the path separator immediately after the folder prefix.

Related

Extract a single file from a ZIP archive

I'm using the next code to download some zip archive:
$client = new-object System.Net.WebClient
$client.DownloadFile("https://chromedriver.storage.googleapis.com/$LatestChromeRelease/chromedriver_win32.zip","D:\MyFolder.zip")
As the result I get the ZIP archive "MyFolder.zip" that contains a required file (lets imagine 'test.txt').
How I can extract this particular file from the ZIP archive into a given folder?
PowerShell 4+ has an Expand-Archive command but as of PS 7.2.3 it can only extract the archive completely. So extract it to a temporary directory and copy the file you are interested in.
If you have PS 5.1+ available, scroll down for a more efficient solution that uses .NET classes.
$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'
# Relative path of file in ZIP to extract.
$fileToExtract = 'test.txt'
# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force
# Create a unique temporary directory
$tempDir = Join-Path ([IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('n'))
$null = New-Item $tempDir -ItemType Directory
try {
# Extract archive to temp dir
Expand-Archive -LiteralPath $archivePath -DestinationPath $tempDir
# Copy the file we are interested in
$tempFilePath = Join-Path $tempDir $fileToExtract
Copy-Item $tempFilePath $destinationDir
}
finally {
# Remove the temp dir
if( Test-Path $tempDir ) {
Remove-Item $tempDir -Recurse -Force -EA Continue
}
}
With PS 5.1+ you can use .NET classes to directly extract a single file (without having to extract the whole archive):
# Load required .NET assemblies. Not necessary on PS Core 7+.
Add-Type -Assembly System.IO.Compression.FileSystem
$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'
# Relative path of file in ZIP to extract.
# Use FORWARD slashes as directory separator, e. g. 'subdir/test.txt'
$fileToExtract = 'test.txt'
# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force
# Convert (possibly relative) paths for safe use with .NET APIs
$resolvedArchivePath = Convert-Path -LiteralPath $archivePath
$resolvedDestinationDir = Convert-Path -LiteralPath $destinationDir
$archive = [IO.Compression.ZipFile]::OpenRead( $resolvedArchivePath )
try {
# Locate the desired file in the ZIP archive.
# Replace $_.Fullname by $_.Name if file shall be found in any sub directory.
if( $foundFile = $archive.Entries.Where({ $_.FullName -eq $fileToExtract }, 'First') ) {
# Combine destination dir path and name of file in ZIP
$destinationFile = Join-Path $resolvedDestinationDir $foundFile.Name
# Extract the file.
[IO.Compression.ZipFileExtensions]::ExtractToFile( $foundFile[ 0 ], $destinationFile )
}
else {
Write-Error "File not found in ZIP: $fileToExtract"
}
}
finally {
# Close the archive so the file will be unlocked again.
if( $archive ) {
$archive.Close()
$archive.Dispose()
}
}
Notes:
Convert-Path should be used when passing PowerShell paths that might be relative paths, to .NET APIs. The .NET framework has its own current directory, which doesn't necessarily match PowerShell's. Using Convert-Path we convert to absolute paths so the current directory of .NET is no longer relevant.
.Where and .ForEach are PowerShell intrinsic methods that are available on all objects. They are similar to the Where-Object and ForEach-Object commands but more efficient. Passing 'First' as the 2nd argument to .Where stops searching as soon as we have found the file.
Note that .Where always outputs a collection, even if only a single element matches. This is contrary to Where-Object which returns a single object if only a single element matches. So we have to write $foundFile[ 0 ] when passing it to function ExtractToFile, instead of just $foundFile which would be an array.

Powershell dropping characters while creating folder names

I am having a strange problem in Powershell (Version 2021.8.0) while creating folders and naming them. I start with a number of individual ebook files in a folder that I set using Set-Location. I use the file name minus the extension to create a new folder with the same name as the e-book file. The code works fine the majority of the time with various file extensions I have stored in an array beginning of the code.
What's happening is that the code creates the proper folder name the majority of the time and moves the source file into the folder after it's created.
The problem is, if the last letter of the source file name, on files with the extension ".epub" end in an "e", then the "e" is missing from the end of the created folder name. I thought that I saw it also drop "r" and "p" but I have been unable to replicate that error recently.
Below is my code. It is set up to run against file extensions for e-books and audiobooks. Please ignore the error messages that are being generated when files of a specific type don't exist in the working folder. I am just using the array for testing and it will be filled automatically later by reading the folder contents.
This Code Creates a Folder for Each File and moves the file into that Folder:
Clear-Host
$SourceFileFolder = 'N:\- Books\- - BMS\- Books Needing Folders'
Set-Location $SourceFileFolder
$MyArray = ( "*.azw3", "*.cbz", "*.doc", "*.docx", "*.djvu", "*.epub", "*.mobi", "*.mp3", "*.pdf", "*.txt" )
Foreach ($FileExtension in $MyArray) {
Get-ChildItem -Include $FileExtension -Name -Recurse | Sort-Object | ForEach-Object { $SourceFileName = $_
$NewDirectoryName = $SourceFileName.TrimEnd($FileExtension)
New-Item -Name $NewDirectoryName -ItemType "directory"
$OriginalFileName = Join-Path -Path $SourceFileFolder -ChildPath $SourceFileName
$DestinationFilename = Join-Path -Path $NewDirectoryName -ChildPath $SourceFileName
$DestinationFilename = Join-Path -Path $SourceFileFolder -ChildPath $DestinationFilename
Move-Item $OriginalFileName -Destination $DestinationFilename
}
}
Thanks for any help you can give. Driving me nuts and I am pretty sure it's something that I am doing wrong, like always.
String.TrimEnd()
Removes all the trailing occurrences of a set of characters specified in an array from the current string.
TrimEnd method will remove all characters that matches in the character array you provided. It does not look for whether or not .epub is at the end of the string, but rather it trims out any of the characters in the argument supplied from the end of the string. In your case, all dots,e,p,u,b will be removed from the end until no more of these characters are within the string. Now, you will eventually (and you do) remove more than what you intended for.
I'd suggest using EndsWith to match your extensions and performing a substring selection instead, as below. If you deal only with single extension (eg: not with .tar.gz or other double extensions type), you can also use the .net [System.IO.Path]::GetFileNameWithoutExtension($MyFileName) method.
$MyFileName = "Teste.epub"
$FileExt = '.epub'
# Wrong approach
$output = $MyFileName.TrimEnd($FileExt)
write-host $output -ForegroundColor Yellow
#Output returns Test
# Proper method
if ($MyFileName.EndsWith($FileExt)) {
$output = $MyFileName.Substring(0,$MyFileName.Length - $FileExt.Length)
Write-Host $output -ForegroundColor Cyan
}
# Returns Tested
#Alternative method. Won't work if you want to trim out double extensions (eg. tar.gz)
if ($MyFileName.EndsWith($FileExt)) {
$Output = [System.IO.Path]::GetFileNameWithoutExtension($MyFileName)
Write-Host $output -ForegroundColor Cyan
}
You're making this too hard on yourself. Use the .BaseName to get the filename without extension.
Your code simplified:
$SourceFileFolder = 'N:\- Books\- - BMS\- Books Needing Folders'
$MyArray = "*.azw3", "*.cbz", "*.doc", "*.docx", "*.djvu", "*.epub", "*.mobi", "*.mp3", "*.pdf", "*.txt"
(Get-ChildItem -Path $SourceFileFolder -Include $MyArray -File -Recurse) | Sort-Object Name | ForEach-Object {
# BaseName is the filename without extension
$NewDirectory = Join-Path -Path $SourceFileFolder -ChildPath $_.BaseName
$null = New-Item -Path $NewDirectory -ItemType Directory -Force
$_ | Move-Item -Destination $NewDirectory
}

Move every folder to top level including content recursively in Powershell

how do I recursively move all directories to the top level including all their sub directories.
The files in the directories should be copied as well.
And if a directory already exists its contents should be merged and all the files should remain (maybe via renaming the files)
e.g.
dir1
----img1
----img2
----dir2
--------img1
--------img2
------------dir1
------------img1
------------img2
------------img3
dir4
----img1
----img2
----img3
becomes
dir1
----img1
----img1_1
----img2
----img2_2
----img3
dir2
----img1
----img2
dir4
----img1
----img2
----img3
My approach was something like that.
Get-ChildItem $SOURCE_PATH -Recurse |
Foreach-Object {
$IS_DIR = Test-Path -Path $_.FullName -PathType Container
if ($IS_DIR) {
Move-Item $_.FullName -dest ($DESTPATH + "/" + $_.Name)
}
}
thanks.
Instead of moving directories I would move individual files to have control over destination name of each file.
This code is untested, it is just intended to give you an idea. Tweak as needed.
# Use parameter -File to operate on files only
Get-ChildItem $SOURCE_PATH -File -Recurse | Foreach-Object {
# Get name of parent directory
$parentDirName = $_.Directory.Name
# Make full path of destination directory
$destSubDirPath = Join-Path $DESTPATH $parentDirName
# Create destination directory if not exists (parameter -Force).
# Assignment to $null to avoid unwanted output of New-Item
$null = New-Item $destSubDirPath -ItemType Directory -Force
# Make desired destination file path. First try original name.
$destFilePath = Join-Path $destSubDirPath $_.Name
# If desired name already exists, append a counter until we find an unused name.
$i = 1
while( Test-Path $destFilePath ) {
# Create a new name like "img1_1.png"
$destFilePath = Join-Path $destSubDirPath "$($_.BaseName)_$i.$($_.Extension)"
$i++
}
# Move and possibly rename file.
Move-Item $_ $destFilePath
}

Powershell - Get path location via Find-Path Function

So i wrote the code below
$datum_vandaag = $(Get-Date).toString('yyyy-MM-dd')
$maand = $datum_vandaag.substring(5, 2)
if (-Not (Test-Path -Path "C:\Users\Nick\Desktop\ITIL\**" -Include *$maand*)) { #(contains words gelijk aan (get-date)
md -Path "C:\Users\Nick\Desktop\ITIL\[??????]\$datum_vandaag" # Makes folder with name of current date in path:
}
Else{Write-Output "test output"
}
The IF function is looking if in the path Desktop\ITIL** there is a folder that has the same number as the current date (07 is july) somewhere in the name of the folder.
Now i would like to make a new folder in the folder that is found by the command below :
Test-Path -Path "C:\Users\Nick\Desktop\ITIL\**" -Include *$maand*
How could i get acces to this path so i can use it in the md command (now marked with [??????] in the code) Because currently i only receive if a folder is found, false or true.
All i could find what possibly would work is get-path or resolve-path but I don't know how to implement this.
It's difficult to fully understand what you are asking, but I think the command you are looking for is Get-Item which will return an object to the named folder.
Something like this might work:
$path = Get-Item -Path "C:\Users\Nick\Desktop\ITIL\**" -Include *$maand*
if ($path) {
$newPath = Join-Path -Path $path -ChildPath $datum_vandaag
New-Item -Path $newPath -ItemType Directory
}

Powershell Move file to new destination based on trimmed file name

I have a folder where files get dropped, I wish to pull the files from that folder and move to a new folder based on part of the file name. If the new folder is missing then create it.
I have attempted to put together the below, however it throws an error about the path already existing and doesn't move the file.
File names can be any thing with out pattern except the last 16 characters of the file, I am removing these and using the remaining as the folder name.
I am really new to scripting so if i have made a silly mistake explanations are appreciated.
Edit
I have played with different orders of operations, added a "-Force" to the new item command, tried with using "Else" and not "If (!(".
I am now at the point where it proudly displays the new directory and then stops.
Could i add the move-item part to a new for each loop so it is processed after the dir is created and tested? If so how do you arrange the { } parts?
Edit 2
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me.
Thanks every one for your input.
Updated script 3.0
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}
You may have to tweak the paths being used but the below should work.
$sourcefiles = ((Get-ChildItem $source -Filter $filter).BaseName).TrimEnd(16)
foreach ($file in $sourcefiles)
{
if(!(Test-Path "$dest\$file")){
New-item -ItemType directory -path "$dest\$file"
}
Move-Item "$source\$file" "$dest\file"
}
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me. Thanks every one for your input.
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}