Powershell: subtract $pwd from $file.Fullname - powershell

Given the following files:
c:\dev\deploy\file1.txt
c:\dev\deploy\file2.txt
c:\dev\deploy\file3.txt
c:\dev\deploy\lib\do1.dll
c:\dev\deploy\lib\do2.dll
e.g. if $pwd is the following
c:\dev\deploy
running the statement
$files = get-childitem
I want to take this list and using foreach ($file in $files) I want to substitute my own path for the $pwd e.g. I want to print c:\temp\files like the following:
c:\temp\files\file1.txt
c:\temp\files\file2.txt
c:\temp\files\file3.txt
c:\temp\files\lib\do1.dll
c:\temp\files\lib\do2.dll
How can I peform this i.e.
A = c:\dev\deploy\file1.txt - c:\dev\deploy\
B = c:\temp\files\ + A
giving B = c:\temp\files\file1.txt
?

I would use filter here and consider piping the files like this:
filter rebase($from=($pwd.Path), $to) {
$_.FullName.Replace($from, $to)
}
You can call it like this:
Get-ChildItem C:\dev\deploy | rebase -from C:\dev\deploy -to C:\temp\files\
Get-ChildItem | rebase -from (Get-Location).path -to C:\temp\files\
Get-ChildItem | rebase -to C:\temp\files\
Note that the replacing is case sensitive.
In case you would need case insensitive replace, regexes would help:
(edit based on Keith's comment. Thanks Keith!)
filter cirebase($from=($pwd.Path), $to) {
$_.Fullname -replace [regex]::Escape($from), $to
}

There's a cmdlet for that, Split-Path, the -leaf option gives you the file name. There's also Join-Path, so you can try something like this:
dir c:\dev\deploy | % {join-path c:\temp\files (split-path $_ -leaf)} | % { *action_to_take* }

How about something like:
function global:RelativePath
{
param
(
[string]$path = $(throw "Missing: path"),
[string]$basepath = $(throw "Missing: base path")
)
return [system.io.path]::GetFullPath($path).SubString([system.io.path]::GetFullPath($basepath).Length + 1)
}
$files = get-childitem Desktop\*.*
foreach($f in $files)
{
$path = join-path "C:\somepath" (RelativePath $f.ToString() $pwd.ToString())
$path | out-host
}
I took the simple relative path from here although there is some problems with it, but as you only want to handle paths below your working directory it should be okay.

This works pretty well for me:
gci c:\dev\deploy -r -name | %{"c:\temp\$_"}

The accepted answer only works without Sub-Folders
if you need to "convert" Sub-Folders, the Answer of #stej is better.
Here my Version:
Get-ChildItem -Recurse | ForEach-Object { $_.Fullname.Replace('D:\Work\Test\Release', 'C:\temp\files') }
Note: .Replace doesn't need to be escaped

Related

Powershell Script, 2x variable in foreach loop

I'm losing my mind. I have to confess I'm a typical copy-paste non-scripting guy, stand here with something new I cannot solve. I want to work with ocrmypdf.exe where I have to read a network-folder for PDFs and put it on a subfolder.
ocrmypdf works simple: ocrmypdf.exe
I have 3 variables like:
$source = #(Get-ChildItem -Path 'X:\OCR\*.pdf') # <-- here are my files, filtered for pdfs
$destname = "X:\ocr\done" #destination-folder where the pdf-files should be written in
$destfiles = foreach ($file in $source) {"$destname\$($file.name)"} # <--- destination path + the same source-file-name
when I have to run a command-exe in Powershell, I should run it like
Foreach ($a in $source)
{
& $command $param
}
where $command and $param is (not) something like this:
$command = 'ocrmypdf.exe'
$param = '$source', '$destfiles'
but as I already know this is not working because the foreachloop can not work with my variables.
Could someone please help me to solve this? Yes my laziness reading a powershell-book comes over and over me now, but I try my luck anyway :)
Thank you in advance
You can supply as many arguments to a command invocation as you want:
$source = #(Get-ChildItem -Path 'X:\OCR\*.pdf')
$destname = "X:\ocr\done"
$command = 'ocrmypdf.exe'
foreach ($file in $source) {
$sourcePath = $file.FullName
$destPath = Join-Path $destname $file.name
# pass both arguments to command
& $command $sourcePath $destPath
}

Find files with partial name match and remove desired file

I have a little over 12000 files that I need to sort through.
18-100-00000-LOD-H.pdf
18-100-00000-LOD-H-1C.pdf
21-200-21197-LOD-H.pdf
21-200-21197-LOD-H-1C.pdf
21-200-21198-LOD-H.pdf
21-200-21198-LOD-H-1C.pdf
I need a way to go through all the files and delete the LOD-H version of the files.
EX:
21-200-21198-LOD-H.pdf
21-200-21198-LOD-H-1C.pdf
With the partial match being the 5 digit code I need a script that would delete the LOD-H case of the partial match.
So far this is what I have but it won't work because I need to supply values for the pattern but since there isn't one set pattern and more like multiple patterns I don't know what to supply it with
$source = "\\Summerhall\GLUONPREP\Market Centers\~Pen Project\Logos\ALL Office Logos"
$destination = "C:\Users\joshh\Documents\EmptySpace"
$toDelete = "C:\Users\joshh\Documents\toDelete"
$allFiles = #(Get-ChildItem $source -File | Select-Object -ExpandProperty FullName)
foreach($file in $allFiles) {
$content = Get-Content -Path $file
if($content | Select-String -SimpleMatch -Quiet){
$dest = $destination
}
else{
$dest = $toDelete
}
}
Any help would be super appreciated, even links to something similar or even links to documentation so I can start piecing a script of my own would be super helpful.
Thank you!
This should work for what you need:
# Get a list of the files with -1C preceeding the extension
$1cFiles = #( ( Get-ChildItem -File "${source}/*-LOD-H-1C.pdf" ).Name )
# Retreive files that match the same pattern without 1C, and iterate over them
Get-ChildItem -File "${source}/*-LOD-H.pdf" | ForEach-Object {
# Get the name of the file if it had the -1C suffix preceeding the .ext
$useName = $_.Name.Insert($_.Name.LastIndexOf('.pdf'), '-1C')
# If the -1C version of the file exists, remove the current (non-1C) file
if( $1cFiles -contains $useName ) {
Remove-Item -Force $_
}
}
Basically, look for the 1C files in $toDelete, then iterate over the non-1C files in $toDelete, removing the non-1C file if adding -1C before the file extension matches an existing file with 1C in the name.

Looping through File Groups such as FileGroup159, FileGroup160, etc. in Powershell

So I got the code to work how I like it for individual files. Based on some of the suggestions below, I was able to come up with this:
$Path = "C:\Users\User\Documents\PowerShell\"
$Num = 160
$ZipFile = "FileGroup0000000$Num.zip"
$File = "*$Num*.txt"
$n = dir -Path $Path$File | Measure
if($n.count -gt 0){
Remove-Item $Path$ZipFile
Compress-Archive -Path $Path$File -DestinationPath $Path
Rename-Item $Path'.zip' $Path'FileGroup0000000'$Num'.zip'
Remove-Item $Path$File
}
else {
Write-Output "No Files to Move for FileGroup$File"
}
The only thing I need to do now is have $Num increment after the program finishes each time. Therefore the program will run, and then move $Num to 160, 161, etc. and I will not have to re initiate the code manually. Thanks for the help so far.
Your filename formatting should go inside the loop and you should use the Format operator -f to get the preceding zeros, like:
159..1250 | ForEach-Object {
$UnzippedFile = 'FileGroup{0:0000000000}' -f $_
$ZipFile = "$UnzippedFile.zip"
Write-Host "Unzipping: $ZipFile"
# Do your thing here
}

Searching for files in powershell

I've scoured all of the internet for this answer. Maybe it's right here, but alas, I'm out of time and we're on a time schedule from the wonderful boys over in legal.
We have some files which need to be retrieved based on particular names which appear in the directory path.
The person who stored and saved all of these files kept the same naming convention throughout. She's pretty awesome and a++ to her.
The file structure is as below:
Animals
-Dogs
-Folders With Breeds of Dogs
-<Breed of Dog>_MA_etc.pdf
-Cats
-Folders with Breeds of Cats
-<Breed of Cat>_MA_etc.pdf
-ETC
-etc
-etc
The person who saved the files was meticulous about file structure and naming convention, so you can expect c:\animals\dogs\GSD\GSD_MA.PDF or something like that.
While the original author was rather consistent, human error has occured so what I'm trying to do is look for "close enough", basically.
We might have:
Client Agreements\Netflix\files
Master Agreements\Netflix,Inc\files
Rental Agreements\Netflix\files
What I want to do is grab the file structure of all of those and move them to my "E:\sorted" directory maintaining the file structure it has.
So stepping way from animals, we've got a client list from legal with names they're interested. If I look for name:name, i get 27 results. So far not good.
I've tried partial and I get zero results. So here's my terrible code below. Maybe you can make fun of me and show me where I went wrong.
$a = Import-CSV C:\scripts\Clients.csv
$a = #($a.Client)
#$a = $a | %{ $_.SubString(0,6) }
$c = Get-ChildItem E:\Legal\ -include ($a) -recurse # | Where-Object {($_ -match $a)}
ForEach($file in $c){
$dest = Split-Path -path $file.FullName -Parent | Split-path -NoQualifier
#Copy-Item -path $file -recurse -Destination "e:\sorted\11\$dest" -force -Verbose
}
I expect that there is a more PowerShell-ish way to do it, but I used a more procedural-type approach.
Using a HashSet, I create a set of directories which need to be copied. A HashSet has only one of an entry, so if it contains "C:\A\B", then adding "C:\A\B" again will not add another entry.
The .contains method is the .NET one, not the PS one, and similarly for .replace.
$src = "C:\temp\a"
$dest = "F:\temp\b"
$CsvFile = Join-Path -Path $src -ChildPath "findthese.csv"
$sought = (Import-Csv $CsvFile).Client
$dirs = Get-ChildItem -Path $src -Directory -Recurse
$set = New-Object System.Collections.Generic.HashSet[string]
# get the directories with a client name in the path anywhere
foreach($dir in $dirs) {
foreach($client in $sought) {
if ($dir.FullName.contains($client)) {
$temp = $set.Add($dir.FullName)
}
}
}
# copy the selected directory structures to the destination
foreach($dir in $set) {
Copy-Item -path $dir -Destination $dir.replace($src, $dest) -Recurse -WhatIf
}
I left the -WhatIf in there so you can quickly check it's going to do the right thing.
If the names in $a don't exactly match the names of files, using that as input to the include parameter won't help you find just those files you want.
I've got a file named clients.csv with the follwong
client,gender,fun
fred,m,y
barney,m,y
wilma,f,y
navneet,n,y
kumar,f,y
konda,m,y
In my current directory, I've got a directory named clients with the following contents:
C:
├───clients
├───losers
│ barney_loser.txt
│ kumar_loser.txt
│
└───winners
fred_winner.txt
konda_winner.txt
wilma_winner.txt
Case-1:
ls .\clients\ -Filter *.txt -Recurse
Returns all the text files.
Case-2:
$people = import-csv -path .\people.csv
$clients = $people.client
ls .\clients\ -Filter *.txt -Recurse -Include $clients
Returns me nothing.
Case-3:
$people = import-csv -path .\people.csv
$clients = $people.client
$clients += 'kumar_loser.txt'
ls .\clients\ -Filter *.txt -Recurse -Include $clients
Returns me one record for "kumar_loser.txt".
I'm asserting the pattern in your list ($a) don't match the file names.
If I wanted to fix that in my example, I could do something like this...
$people = import-csv -path .\people.csv
$clients = $people.client
for($i = 0; $i -lt $clients.length; $i++) {
$clients[$i] = '*{0}*' -f $clients[$i]
}
ls .\clients\ -Filter *.txt -Recurse -Include $clients
Hope this helps.
Thanks for the help guys.
I took a less scripty approach and procedural approach, as suggested above. Here's the code I used that mostly worked, a colleague and I went through and verified the results and some outlier files. I had to double check the errors that popped up and found a few more files that I wanted. Wasn't perfect, but definitely cut down looking through 700 folders and 3000 files. Include is great but filter is what I really wanted. Furthermore, Include doesn't like index values and Filter Especially doesn't, so I had to save it to a variable and filter by that with a * wildcard which did work.
Here's what I did:
$people = Import-CSV C:\scripts\HelenClients.csv
$clients = $people.Client| %{$_.SubString(0,5)}
for($i=0; $i -lt $clients.Length; $i++){
$name = $clients[$i]
Write-Host "Searching for $name"
$file = Get-ChildItem 'E:\Legal\' -Include "$name*" -recurse
if($file -ne $null){
$dest = Split-Path -path $file -Parent
$dest1 = $dest | Split-Path -NoQualifier
$from = $dest[0]
$to = $dest1[0]
$too = $file.BaseName[0]
Copy-Item $file -Destination e:\sorted\16\$to\$too\ -force -Verbose
}
else{
Write-Output "No results found"
}
}
I found when you store the results into a variable, if there's more than one, it'll list all of the locations and names, etc. Not pretty. See below:
PS C:\Users\me> $ff
Directory: E:\ParentDir\subfolder\redacted
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/4/2018 1:47 PM 50485 redacted.docx
-a---- 6/4/2018 1:47 PM 155579 redacted.pdf
PS C:\Users\me> $ff.Basename
Redacted Basename 0
Redacted Basename 1
PS C:\Users\me> $ff.BaseName[0]
Redacted Basename 0
So I just wanted the first indexed value. I also wanted to maintain the file structure without copying everything over, so I used split-path to kind of take it apart. It's very hodgepodge and not pretty to look at, but it works.

DotNetZip, Powershell and Relative Paths

I want to zip some selected folders into an archive only with their relative paths.
Some of them have the same folder names (all release-folders, all debug-folders).
The filtering works fine and I have called set-location prior to get-children command.
What is the easiest way to do this job?
Do I really have to implement something like this
foreach ($o in $children)
{ $relPath = $o.FullName.Substring(subPath.Length);
$relPath = $relPath.Substring(0, relPath.LastIndexOf(#"\"));
zip.AddFolder($o.Name, $relPath);
}
Does someone can provide me an example?
Thx
When I need to handle relative paths, I do something like this:
$basePath = "C:\MyBasePath"
$newBasePAth = "C:\NewBasePath"
$files = Get-ChildItem $basePath
$newFileNames = foreach ($f in $files) {
$f.Fullname.Replace($basePath, $newBasePath)
}
Make sure the pattern of slashes you use is the same in $basePath and $newBasePath (use .Trim() to be sure)
Hope this helps
I finally implemented this and it works with the relative paths:
function ZipUp-Files ( $mychildren )
{
foreach ($o in $mychildren)
{
$e= $zipfile.AddDirectory($o.FullName,$o.fullname.substring($pwd.path.length));
}
}
$children =get-childitem -recurse -force | where-object {$_.psiscontainer} | where {$_.name -match $teststring}
[System.Reflection.Assembly]::LoadFrom("C:\dotNetZip\zip-v1.9\Release\Ionic.Zip.dll");
$zipfile = new-object Ionic.Zip.ZipFile($ziptarget);
$zipfile.UseZip64WhenSaving= [Ionic.Zip.Zip64Option]::Always
$zipfile.MaxOutPutSegmentSize=734003200
ZipUp-Files $children
$zipfile.Save("c:\temp\arc_rel.zip")
$zipfile.Dispose()