Powershell rename files with sequential numbers in the middle of name - powershell

I've never used powershell or any cmd line to try and rename files, nor so I really know much about script writing in general.
I've already had some success in renaming the files in question but am stuck on the last piece of the puzzle.
Original file names:
NEE100_N-20210812_082245.jpg
NEE101_E-20210812_083782.jpg
NEE102_W-20210812_084983.jpg
I successfully change those to AT-###-N-......jpg using:
Rename-Item -NewName {$_.name -replace "NEE\d\d\d_", "AT-112-"}
And this is what they looked like after:
AT-112-N-20210812_082245.jpg
AT-112-E-20210812_083782.jpg
AT-112-W-20210812_084983.jpg
Now however, I have a few files that look like this:
AT-112-NewImage-20210812_083782.jpg
AT-112-NewImage-20210812_093722.jpg
and I want to change them to:
AT-112-D1-20210812_083782.jpg
AT-112-D2-20210812_093722.jpg
...and so on.
I've tried a few things here to try and do that. Such as replacing "NewImage" with "D" and then using something like this (not exact, just an example):
$i = 1
Get-ChildItem *.jpg | %{Rename-Item $_ -NewName ('19981016_{0:D4}.jpg' -f $i++)}
But this did not work. I have seen scripts that use sequential numbering either added as a suffix or a prefix. But I can't figure out how to do this if what I want to have sequence numbering in the middle of the name.
Hopefully this make sense, if I need more elaboration, let me know. Thanks!

You need to use an expression (inside (...)) as your -replace substitution operand in order to incorporate a dynamic value, such as the sequence number in your case.
In order to use a variable that maintains state across multiple invocations of a delay-bind script block ({ ... }, the one being passed to the -NewName parameter in your first attempt), you need to create the variable in the caller's scope and explicitly reference it there:
This is necessary, because delay-bind script blocks run in a child scope, unfortunately,[1] so that any variables created inside the block go out of scope after every invocation.
Use Get-Variable to obtain a reference to a variable object in the caller's (parent) scope[2], and use its .Value property, as shown below.
$i = 1
Get-ChildItem *.jpg | Rename-Item -NewName {
$_.Name -replace '-NewImage-', ('-D{0}-' -f (Get-Variable i).Value++)
} -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note: The above solution is simple, but somewhat inefficient, due to the repeated Get-Variable calls - see this answer for more efficient alternatives.
[1] This contrasts with the behavior of script blocks passed to Where-Object and ForEach-Object. See GitHub issue #7157 for a discussion of this problematic discrepancy.
[2] Without a -Scope argument, if Get-Variable doesn't find a variable in the current scope, it looks for a variable in the ancestral scopes, starting with the parent scope - which in this case the caller's. You can make the call's intent more explicitly with -Scope 1, which starts the lookup from the parent scope.

Related

Rename files & append Number in Powershell [duplicate]

I've never used powershell or any cmd line to try and rename files, nor so I really know much about script writing in general.
I've already had some success in renaming the files in question but am stuck on the last piece of the puzzle.
Original file names:
NEE100_N-20210812_082245.jpg
NEE101_E-20210812_083782.jpg
NEE102_W-20210812_084983.jpg
I successfully change those to AT-###-N-......jpg using:
Rename-Item -NewName {$_.name -replace "NEE\d\d\d_", "AT-112-"}
And this is what they looked like after:
AT-112-N-20210812_082245.jpg
AT-112-E-20210812_083782.jpg
AT-112-W-20210812_084983.jpg
Now however, I have a few files that look like this:
AT-112-NewImage-20210812_083782.jpg
AT-112-NewImage-20210812_093722.jpg
and I want to change them to:
AT-112-D1-20210812_083782.jpg
AT-112-D2-20210812_093722.jpg
...and so on.
I've tried a few things here to try and do that. Such as replacing "NewImage" with "D" and then using something like this (not exact, just an example):
$i = 1
Get-ChildItem *.jpg | %{Rename-Item $_ -NewName ('19981016_{0:D4}.jpg' -f $i++)}
But this did not work. I have seen scripts that use sequential numbering either added as a suffix or a prefix. But I can't figure out how to do this if what I want to have sequence numbering in the middle of the name.
Hopefully this make sense, if I need more elaboration, let me know. Thanks!
You need to use an expression (inside (...)) as your -replace substitution operand in order to incorporate a dynamic value, such as the sequence number in your case.
In order to use a variable that maintains state across multiple invocations of a delay-bind script block ({ ... }, the one being passed to the -NewName parameter in your first attempt), you need to create the variable in the caller's scope and explicitly reference it there:
This is necessary, because delay-bind script blocks run in a child scope, unfortunately,[1] so that any variables created inside the block go out of scope after every invocation.
Use Get-Variable to obtain a reference to a variable object in the caller's (parent) scope[2], and use its .Value property, as shown below.
$i = 1
Get-ChildItem *.jpg | Rename-Item -NewName {
$_.Name -replace '-NewImage-', ('-D{0}-' -f (Get-Variable i).Value++)
} -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note: The above solution is simple, but somewhat inefficient, due to the repeated Get-Variable calls - see this answer for more efficient alternatives.
[1] This contrasts with the behavior of script blocks passed to Where-Object and ForEach-Object. See GitHub issue #7157 for a discussion of this problematic discrepancy.
[2] Without a -Scope argument, if Get-Variable doesn't find a variable in the current scope, it looks for a variable in the ancestral scopes, starting with the parent scope - which in this case the caller's. You can make the call's intent more explicitly with -Scope 1, which starts the lookup from the parent scope.

Removing the front part of a string based on an specific character. (\)

I first create my array with a list of files in a directory (and subdirectories) using the Cmdlet Get-ChildItem, and store them in a variable
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak).FullName
I echo the variable ($PSVariable), this is my output (as desired):
F:\SQL_Backups\INTRAPORTAL\StoreDevelopment\StoreDevelopment_backup_2021_02_11_003002_3930170.bak
F:\SQL_Backups\INTRAPORTAL\StoreDevelopment\StoreDevelopment_backup_2021_02_12_003002_4780885.bak
F:\SQL_Backups\JDASQL\DEVMOD\DEVMOD_backup_2021_02_10_190002_5130923.bak
F:\SQL_Backups\JDASQL\DEVMOD\DEVMOD_backup_2021_02_11_190003_7621021.bak
Goal:
I need to remove the directory path from each array entries so it only contains the file name that will be stored in a temporary variable within a foreach loop:
StoreDevelopment_backup_2021_02_11_003002_3930170.bak
StoreDevelopment_backup_2021_02_12_003002_4780885.bak
DEVMOD_backup_2021_02_10_190002_5130923.bak
DEVMOD_backup_2021_02_11_190003_7621021.bak
Some will recommend simply using (.Name) in the Get-ChildItem command, but I need the array to have both the path and filename (FullName) as the array's contents are being used for other parts of the function. I'm a novice when it comes to regular expressions and I can't seem to get the results in the goal section. I've even tried using trim() methods, but no luck. Any recommendations would greatly be appreciated. Thank you.
Expanding on what #AdminOfThings recommended, you are making more work for yourself than you need. PowerShell is an object based scripting language, so to succeed you should use its full POWER.
The approach you're taking now is to take only one property from this useful object and then find you need to start slicing and dicing it in order to make it work.
There's an easier way. We love easy here, and the easy way to do this is to take the full object and then pick and chose its properties where it makes sense, like this:
$i = 0
#changed to remove the .FullName at then end
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak)
ForEach ($item in $psVariable){
$i++
Write-host "Processing [$($item.Name)], item number $i of $($psVariable.Count)"
Copy-item -Path $item.FullName -Destination C:\temp -WhatIf
}
It gives you meaningful output and then you have the full selection of properties to work with.
The one that makes the most sense to use is just .Name as you reference above. But then you still have .FullName, which includes the qualified path as well.
If you want to see the full selection of properties, try this:
$PsVariable[0] | Format-list *
Offered only as an inferior option to that of FoxDeploy's you can also use Split-Path to get the filename from a path
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak).FullName
$PSVariable | Split-Path -Leaf

In function repeat an action for each entered parameter

My main script run once gci on a specified drive via -path parameter , then it does multiple different tables from this output. Here below is a part of my script which does a specific table from an directory specified via -folder parameter, for example :
my-globalfunction -path d:\ -folder d:\folder
It work fine, but only for one entered folder path, the goal of this script is that user can enter multiple folders path and get a tables for each entered -folder parameter value, like this :
This clause in your Where-Object would be the issue:
$_.FullName.StartsWith($folder, [System.StringComparison]::OrdinalIgnoreCase)
The array of folders passed are most likely being cast as one long string which would never match. I had a regex solution posted but remembered a simpler way after looking at what your logic was trying to do.
Simpler Way
Even easier way is to put this information right into Get-ChildItem since it accepts string arrays for -Path. This way I don't think you even need to have 2 parameters since you never again use the results from $fol anyway. Based on the assumption that you were looking for all subfolders of $folder
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force | Where-Object{$_.psiscontainer}
That would return all subfolders of the paths provided. If you have PowerShell 3.0 or higher this would even be easier.
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force -Directory
Update from comments
The code you have displayed is incomplete which is what lead me to the solution that you see above. If you do use the variable $fol somewhere else that you do not show lets go back to my earlier regex solution which would work better in place with what you already have.
$regex = "^($(($folder | ForEach-Object{[regex]::Escape($_)}) -join "|")).+"
....
$gdfolders = $fol | Where-Object{($_.Attributes -eq "Directory") -and ($_.FullName -match $regex)}
What this will do is build a regex compare string with what I will assume is the logic of locate folders that begin with either of paths passed.
Using your example input of "d:\folder1", "d:\folder2" the variable $regex would work out to ^(d:\\folder1|d:\\folder2). The proper characters, like \, are escaped automatically by the static method [regex]::Escape which is applied to each element. We then use -join to place a pipe which, in this regex capture group means match whats on the left OR on the right. For completeness sake we state that the match has to occur at the beginning of the path with the caret ^ although this is most likely redundant. It would match paths that start with either "d:\folder1" or "d:\folder2". At the end of the regex string we have .+ which means match 1 to more characters. This should ensure we dont match the actual folder "d:\folder1" but meerly its children
Side Note
The quotes in the line with ’Size (MB)’ are not the proper ones which are '. If you have issues around that code consider changing the quotes.

PowerShell: How can I pipeline objects to a parameter that accepts input "ByPropertyName"?

Here is a sample PowerShell script (it doesn't work) that illustrates what I want to do:
$BuildRoot = '_Provided from script parameter_'
$Files = 'a.dll', 'b.dll', 'c.dll'
$BuiltFiles = $Files | Join-Path $BuildRoot
I have a list of filenames, and a directory name, and I want to join them all together, simple. The problem is that this doesn't work because the Join-Path parameter -ChildPath accepts input from the pipeline ByPropertyName, so the following error is reported:
The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input
and its properties do not match any of the parameters that take
pipeline input.
I can "fix" it by changing the line to the following:
$BuiltFiles = $Files | Select #{ Name = "ChildPath"; Expression = {$_}} | join-path $BuildRoot
Basically, the select operation is turning the object into a property value. This works, but it introduces a lot of syntactic noise to accomplish something that seems so trivial. If this is the only way to do it, so be it, but I'd like to make this script maintainable for people in the future, and this is a little hard to grok at first glance.
Is there a cleaner way to accomplish what I'm trying to do here?
You can accomplish this a little easier like so:
$Files = 'a.dll', 'b.dll', 'c.dll'
$Files | Join-Path $BuildRoot -ChildPath {$_}
Note: you don't want to put {} around the files. That creates a scriptblock in PowerShell which is essentially an anonymous function. Also, when a parameter is pipeline bound you can use the trick of supplying a scriptblock ({}) where $_ is defined in that scriptblock to be the current pipeline object.

PowerShell foreach all files and calculate each md5 value

I Have a script to get all html files in a folder,i want to get all md5 values,here's the code:
$Allfiles=get-childItem("*html")
Foreavch-object($Mfile in $Allfiles)
{
$Md5=calMd5($Mfile)
If($HashQueue.contains($Mfile))
{
Continue
}
Else
{Enqueue()}
}
i Can't get the file correctly, how to foreach every file in a dir?
Parentheses are never used when calling a function or cmdlet in PowerShell.
You also have a typo in your foreach-object (note the v in your code).
What is calMd5 expecting as a parameter? A file name? Full path to the file? An object? Same questions for what you're putting into $HashQueue.
Assuming calMd5 can take the path to the file and that's what you're putting into $HashQueue:
$Allfiles = get-childItem -filter *html
Foreach-object($Mfile in $Allfiles)
{
$Md5 = calMd5 $Mfile.FullName
If(!$HashQueue.contains($Mfile.FullName))
{
Enqueue()
}
}
It's not very clear what you're trying to do, but I'm going to infer that you want something like this:
$MD5Hashes = #{}
Get-ChildItem *.html | %{
$MD5Hashes.Add($_.Name, (calMd5 Get-Content $_ | Out-String)
}
This code creates a hashtable of the MD5 hashes of the files, keyed on the filenames, assuming that the calMd5 function takes a string argument. You'd look up the MD5 hash for a file like this: $MD5Hashes.'filename'. Or if you have the filename in a variable, like this: $MD5Hashes."$filename".
A few notes:
It's superfluous to assign the results of Get-ChildItem to a variable and iterate over it with Foreach-Object. Just pipe the results to a Foreach-Object block (%{} is shorthand for Foreach-Object). Object pipelines are one of the central features of PowerShell design. You'll learn to love 'em.
Although Get-ChildItem("*html") will work, it does so indirectly and incidentally. As alroc said, you don't use parentheses to pass arguments to a PowerShell cmdlet or function (though you do use them to pass arguments to methods). In most contexts, parentheses are an expression evaluation operator. The reason this command works is that ("*html") evaluates to the string *html, which is then passed as an argument to Get-ChildItem. In this case, the parentheses are superfluous, but in many cases you'll run into trouble enclosing arguments in parentheses, unless you intend for the argument to be the result of an expression rather than the contents of the parentheses verbatim.
If $HashQueue is a queue object and you want to add the MD5 values to this queue, you'd do this:
$HashQueue.Enqueue((calMd5 Get-Content $_ | Out-String))
However, I'm presuming that's not what you want to do, because it doesn't make sense to me to enqueue the MD5 hashes onto the end of a queue that already contains FileInfo objects representing the files (implied by If($HashQueue.contains($Mfile))).
If this is not what you want to do, please clarify. See the questions in my comment, and please be as specific as possible.