How to check all strings before doing the next step - powershell

I want to perform string-comparison. I use a foreach loop, but I want to do move-item after all strings have been compared.
My concept is " If all of strings not match, do move-item. "
How can I do that?
here is my present code but it is " if match,do move-item
$Myfile=dir *my file name*
$directory=dir D:\example (there are many directory)
foreach($dir in $directory){
if($dir match $Myfile.basename ){
move-item $Myfile.fullname -destination D:\example
}
}
I want to do move-item after string comparison to ensure that my file name doesn't exist $directory
here is what I want to achieve
if( ("all of the directory Name") -notmatch $myfile.basename ){
move-item XXX to XXX
}
if I use -notmatch , it will do move-item at the first time loop. So I need to do move-item after checking them all...

You can set a variable to true if something matches:
$Myfile=dir *my file name*
$directory=dir D:\example (there are many directory)
$move = $false
foreach($dir in $directory){
if($dir match $Myfile.basename ){
$move = $true
}
}
if ($move){
move-item $Myfile.fullname -destination D:\example
}

You can try using notcontains operator to check the file base name appears in the directory files :
$myfile = get-childitem "filepath"
$directory = #(get-childitem "D:\Example" | foreach {$_.basename})
if ($directory -notcontains $myfile.basename) {
move-item xxx to xxx
}

Related

Bulk renaming photos and adding letters to duplicate file names in Powershell

I have a question about a powershell script. I want to rename a bunch of photos within a folder. I have a .csv file of the old names and the new names. This is a section of that file:
OldFile NewFile
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
Many of the new file names will be duplicates. For those files, I want to add a letter (A,B,C, so on) in the middle of the name at an exact location.
For example, if the file, 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg, is a duplicate, I want to add "A" right after "Vicinity", so that the file is called 975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg. The letter will always be at that exact same location (right before the third -).
This is the script I have so far. I know it's not right and I haven't been able to even attempt at adding the letter within the script. (I'm a complete Powershell newbie.)
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
if (Test-Path $file.NewFile) {
$letter = -begin { $count= 1 } -Process { Rename-Item $file.OldFile
"file-$([char](96 + $count)).jpeg"; $count++}
} else {
Rename-Item $file.OldFile $file.NewFile
}
}
Could I get some guidance on how to achieve this file naming system?
Thanks!!!
When renaming files using a character from the alphabet will mean you will only have 26 options. If that is enough for you, you can do the following:
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$folderPath = 'D:\Test'
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
$oldFile = Join-Path -Path $folderPath -ChildPath $file.OldFile
if (Test-Path $oldFile -PathType Leaf) {
# split the new filename into workable parts
$newName = $file.NewFile
$extension = [System.IO.Path]::GetExtension($newName)
$parts = [System.IO.Path]::GetFileNameWithoutExtension($newName) -split '-'
$suffix = $parts[-1]
$prefix = $parts[0..($parts.Count -2)] -join '-'
$charToAppend = 0 # counter to go through the characters in the alphabet. 0..25
while (Test-Path (Join-Path -Path $folderPath -ChildPath $newName) -PathType Leaf) {
if ($charToAppend -gt 25) {
# bail out if al characters have been used up
throw "Cannot rename file '$($file.OldFile)', because all characters A-Z are already used"
}
$newName = '{0}{1}-{2}{3}' -f $prefix, $alphabet[$charToAppend++], $suffix, $extension
}
Rename-Item -Path $oldFile -NewName $newName
}
else {
Write-Warning "File '$($file.OldFile)' not found"
}
}
Before:
D:\TEST
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg
After:
D:\TEST
975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityB-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityC-20200831.jpeg
I think you need to use the method.Insert(). This is a small example how it works:
I ve created a txt named 975NNNN-AGUIRRERM1-Vicinity-20200829.txt in C:\Test just for testing purpose, in your example the first code line is to identify the duplicate(s)
#Code to identify duplicates (insert your code instead of mine)
$files=Get-ChildItem -Path C:\Test -File -Name
#The following line indentifies the location of the last "-" (I understand you always have 3 "-" right?)
$DashPos=($files).LastIndexOf("-")
#This inserts on the position $DashPos, the letter "A")
$files.Insert($DashPos,"A")

Finding n matching files by sub-path recursively inside a directory

I have a file structure like:
RootDir\
ADir
1.xml
2.xml
BDir
1.xml
2.xml
3.xml
CDir
2.xml
3.xml
I need to write script to recursively check if a file exists in more than one directory under RootDir, where the file path is identical from one level below RootDir.
For example RootDir\ADir\1.xml also exists in location RootDir\BDir\1.xml
The code below returns the path of matching entries, however it also continues to match with iteself, despite the -exclude switch
$rootCompare = "C:\Users\XXX\Documents\Compare\RootDir"
$rootItems = get-childitem -Path C:\Users\XXX\Documents\Compare\RootDir -Recurse
foreach ($i in $rootItems){
$dirItems = get-childitem $i -File -Recurse | ForEach-Object {
$fullName = $_.fullName
$baseName = $_.baseName
$baseExt = $_.Extension
Write-Host "fullname="$fullName
if (Test-Path $rootCompare\*\$baseName$baseExt -Exclude $fullName) {
$fileSearch = get-ChildItem -Path $rootCompare\*\$baseName$baseExt -Exclude "*$fullName*"
Write-Host "searching for file: "$fullName
Write-Host "file exists in another location: "$fileSearch
}
}
}
Example return:
fullname= C:\Users\XXX\Documents\Compare\RootDir\CDir\3.xml
searching for file: C:\Users\XXX\Documents\Compare\RootDir\CDir\3.xml
file exists in another location: C:\Users\XXX\Documents\Compare\RootDir\ADir\3.xml
C:\Users\XXX\Documents\Compare\RootDir\CDir\3.xml
1) How can I adapt the exclude condition to prevent it from matching with itself?
2) This version only works at 1 level below RootDir. How should it be adapted to work n levels below RootDir?
For example RootDir\ADir\DDir\1.xml should also match RootDir\BDir\DDir\1.xml but not RootDir\ADir\1.xml
Important point:
The number, name and paths the of directories are dynamic and cannot be hard-coded
The quick and dirty way of how I would do it is to do a single Get-ChildItem -Recurse into a variable. Then I could iterate through that variable twice. Once to get the first list of objects, and a second to compare to the first. Then in the comparison we want to match where the Name is the same, and the FullName doesn't match.
$rootCompare = "C:\Users\XXX\Documents\Compare\RootDir"
$rootItems = get-childitem -Path $rootCompare -Recurse
foreach ($i in $rootItems){
foreach ($j in $rootItems){
if($i.Name -eq $j.Name -and $i.fullName -ne $j.fullName)
{
Write-Host "file" $i.fullName
Write-Host "also exists in another location: "$j.fullName
}
}
}

How can I find out the exact location where the recursive operation is working?

My problem is, that the string for replacement needs to change according to the folder depth the designated file is located and I don't have a clue how to get that info. I need to work with relative addresses.
I want the script to be run from 2 folder levels above the folder where all the files are that need correcting. So I've set the $path in line 1. That folder suppose to be 'depth 0'. In here, the replacement string needs to be in it's native form -> stylesheet.css.
For files in the folders one level below 'depth 0' the string for replacement needs to be prefixed with ../ once -> ../stylesheet.css.
For files in the folders two level below 'depth 0' the string for replacement needs to be prefixed with ../ twice -> ../../stylesheet.css.
...and so on...
I'm stuck here:
$depth = $file.getDepth($path) #> totally clueless here
I need $depth to contain the number of folders under the root $path.
How can I get this? Here's the rest of my code:
$thisLocation = Get-Location
$path = Join-Path -path $thisLocation -childpath "\Files\depth0"
$match = "findThisInFiles"
$fragment = "stylesheet.css" #> string to be prefixed n times
$prefix = "../" #> prefix n times according to folder depth starting at $path (depth 0 -> don't prefix)
$replace = "" #> this will replace $match in files
$depth = 0
$htmlFiles = Get-ChildItem $path -Filter index*.html -recurse
foreach ($file in $htmlFiles)
{
$depth = $file.getDepth($path) #> totally clueless here
$replace = ""
for ($i=0; $i -lt $depth; $i++){
$replace = $replace + $prefix
}
$replace = $replace + $fragment
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace $match, $replace } |
Set-Content $file.PSPath
}
Here's a function I've written that uses Split-Path recursively to determine the depth of a path:
Function Get-PathDepth ($Path) {
$Depth = 0
While ($Path) {
Try {
$Parent = $Path | Split-Path -Parent
}
Catch {}
if ($Parent) {
$Depth++
$Path = $Parent
}
else {
Break
}
}
Return $Depth
}
Example usage:
$MyPath = 'C:\Some\Example\Path'
Get-PathDepth -Path $MyPath
Returns 3.
Unfortunately, I had to wrap Split-Path in a Try..Catch because if you pass it the root path then it throws an error. This is unfortunate because it means genuine errors won't cause an exception to occur but can't see a way around this at the moment.
The advantage of working using Split-Path is that you should get a consistent count regardless of whether a trailing \ is used or not.
Here is a way to get the depth in the folder structure for all files in a location. Hope this helps get you in the right direction
New-Item -Path "C:\Logs\Once\Test.txt" -Force
New-Item -Path "C:\Logs\Twice\Folder_In_Twice\Test.txt" -Force
$Files = Get-ChildItem -Path "C:\Logs\" -Recurse -Include *.* | Select-Object FullName
foreach ($File in $Files) {
[System.Collections.ArrayList]$Split_File = $File.FullName -split "\\"
Write-Output ($File.FullName + " -- Depth is " + $Split_File.Count)
}
Output is this just for illustration
C:\Logs\Once\Test.txt -- Depth is 4
C:\Logs\Twice\Folder_In_Twice\Test.txt -- Depth is 5

Positional parameter cannot be found

I'm not sure why I'm getting the following error
Copy-Item : A positional parameter cannot be found that accepts argument 'C:\Code\PS\Auths\2.jpg'.
At C:\Code\PS\auth-grab.ps1:9 char:12
C:\Code\PS\Auths\2.jpg is the correct path.
(I'm getting one of these for each of the items in the pipeline)
When I echo $rv I get the correct pathand $_ should be right. Where am I going wrong?
oops script below :
function Generate-FileName($fi)
{
$rv = "C:\Code\PS\New\"+ $fi.Name
#echo rv
}
Get-ChildItem Auths\*.* -include 1.jpg, 2.jpg |
ForEach-Object {
Copy-Item $_ -destination Generate-FileName(Get-ChildItem $_)
}
Note if i echo $rv I get the path I want
Wrap the function - Generate-FileName in brackets like this -
ForEach-Object {
Copy-Item $_ -destination (Generate-FileName(Get-ChildItem $_))
}
Enclosing it in parenthesis forces the expression.
OR
Copy the return value of the function in a variable and use the variable in the Copy-Item as below -
function Generate-FileName($fi)
{
"C:\Code\PS\New\"+ $fi.Name
}
Get-ChildItem Auths\*.* -include 1.jpg, 2.jpg |
ForEach-Object {
$destination = Generate-FileName(Get-ChildItem $_)
Copy-Item $_ -destination $destination
}
I think your function Generate-FileName doesn't return anything.
I think your script generate this line with copy-item:
Copy-Item C:\Code\PS\Auths\2.jpg -destination
Try it like this:
function Generate-FileName($fi)
{
$rv = "C:\Code\PS\New\"+ $fi.Name
return $rv # here is the change
}

Get-Item fails with closed pipeline error

If I have an example function ...
function foo()
{
# get a list of files matched pattern and timestamp
$fs = Get-Item -Path "C:\Temp\*.txt"
| Where-Object {$_.lastwritetime -gt "11/01/2009"}
if ( $fs -ne $null ) # $fs may be empty, check it first
{
foreach ($o in $fs)
{
# new bak file
$fBack = "C:\Temp\test\" + $o.Name + ".bak"
# Exception here Get-Item! See following msg
# Exception thrown only Get-Item cannot find any files this time.
# If there is any matched file there, it is OK
$fs1 = Get-Item -Path $fBack
....
}
}
}
The exception message is ... The WriteObject and WriteError methods cannot be called after the pipeline has been closed. Please contact Microsoft Support Services.
Basically, I cannot use Get-Item again within the function or loop to get a list of files in a different folder.
Any explanation and what is the correct way to fix it?
By the way I am using PS 1.0.
This is just a minor variation of what has already been suggested, but it uses some techniques that make the code a bit simpler ...
function foo()
{
# Get a list of files matched pattern and timestamp
$fs = #(Get-Item C:\Temp\*.txt | Where {$_.lastwritetime -gt "11/01/2009"})
foreach ($o in $fs) {
# new bak file
$fBack = "C:\Temp\test\$($o.Name).bak"
if (!(Test-Path $fBack))
{
Copy-Item $fs.Fullname $fBack
}
$fs1 = Get-Item -Path $fBack
....
}
}
For more info on the issue with foreach and scalar null values check out this blog post.
I modified the above code slightly to create the backup file, but I am able to use the Get-Item within the loop successfully, with no exceptions being thrown. My code is:
function foo()
{
# get a list of files matched pattern and timestamp
$files = Get-Item -Path "C:\Temp\*.*" | Where-Object {$_.lastwritetime -gt "11/01/2009"}
foreach ($file in $files)
{
$fileBackup = [string]::Format("{0}{1}{2}", "C:\Temp\Test\", $file.Name , ".bak")
Copy-Item $file.FullName -destination $fileBackup
# Test that backup file exists
if (!(Test-Path $fileBackup))
{
Write-Host "$fileBackup does not exist!"
}
else
{
$fs1 = Get-Item -Path $fileBackup
...
}
}
}
I am also using PowerShell 1.0.