PowerShell foreach all files and calculate each md5 value - powershell

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.

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.

Powershell rename files with sequential numbers in the middle of name

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.

powershell confused about array loop, foreach method, string?

I'm trying to export all CA certificates to a directory in Base64 format, I'm new to powershell, since I'm used to doing scripts with bash. Somehow I'm not seeing something that feels like it should be obvious.
This is my line so far,
#(Get-ChildItem -path Cert:\Localmachine\ca).ForEach({Export-Certificate -Type CERT -FilePath "C:\ssl\certs.d\$_.Thumbprint" -Cert "Cert:\LocalMachine\ca\$_.Thumbprint"})
I appreciate any help, as I'm trying to learn how to be idiomatic in PS4.
This line of code contains 3 issues:
First. String interpolation with object property. PS parser doesn't understand "$var.Property", it only understands $expression within "string". But since it's expression, and not just variable name, you can make PS evaluate your line with "$(something to evaluate)". In other words, your -FilePath should be:
-FilePath "C:\ssl\certs.d\$($_.Thumbprint)"
Second. Working with objects. PS underneath is full-blown .Net Framework. Even though many objects are represented in output in a simple, predefined way, actually they are |ed to output as complete live objects. According to MSDN, the -Cert parameter is a <Certificate>, not a string pointing to a certificate, so your -Cert should be simply
-Cert $_
Third. Arrays. Get-ChildItem underneath is nothing more than DirectoryInfo.GetFileSystemInfos() which returns an array of objects. So ideally, you don't need to wrap it with anything and it's possible to simply pipe it further (Get-ChildItem | Foreach-Object{...}). But many people have different tastes on PS syntax, so the form of (gci).ForEach({...}) (without #) has the right to live as well. But what you are doing in form of #(...) is creating a new array of one item being the array returned to you by gci. So technically, it just shouldn't work. It will though, because PS saves you from such mistakes automatically: in PS you can work with array of 1 item in the same way as with this item directly (unless explicitly specified opposite). To illustrate,
#(4).Length # returns 1
#(#(2,3)).Length # returns 2
#(,#(2,3)).Length #returns 1
Thus, your current syntax for Get-ChildItem is error-prone and relies on automatic PS error-handling sugar. I recommend to either remove # in the beginning, or to rewrite in form of
Get-ChildItem -...... | Foreach-Object {...}

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.

Is there an equivalent of "this" in powershell?

Basically I have this code:
$file = $web.GetFile("Pages/default.aspx")
$file.CheckOut()
and I was wondering if there is anyway to use a pipe and the powershell equivalent of this to rewrite it as:
$web.GetFile("Pages/default.aspx") | $this.CheckOut()
When I try this I get the error:
Expressions are only allowed as the first element of a pipeline.
I also tried using $_ instead of $this but got the same error.
Actually there is a $this in a few cases. You can create a ScriptProperty or ScriptMethod and attach it to an object, and $this will be the original object. You can then define these in types files (I'd recommend using the module EZOut, it makes life much easier) so that any time you see that type, you get that method.
For example:
$Web | Add-Member ScriptMethod EditFile { $this.Checkout() }
Hope this helps
What you're looking for is $_ and it represents the current object in the pipeline. However you can only access $_ in a scriptblock of a command that takes pipeline input e.g.:
$web.GetFile("Pages/default.aspx") | Foreach-Object -Process {$_.Checkout()}
However there are aliases for the Foreach-Object cmdlet {Foreach and %} and -Process is the default parameter so this can be simplified to:
$web.GetFile("Pages/default.aspx") | Foreach {$_.Checkout()}
One other point, the GetFile call appears to return a single file so in this case, the following would be the easiest way to go:
$web.GetFile("Pages/default.aspx").Checkout()
Of course, at this point you no longer have a variable containing the file object.
$_ is the variable for "current object" in powershell.
However, you aren't passing any data, this is just variable assignment. You can only use the pipeline if you manipulate the actual output of a command and use it as input down the pipeline.
I think what you want can be accomplish with nested parentheses:
($web.GetFile("Pages/default.aspx")).CheckOut()
In PS, anything you put inside parentheses gets treated as its own object, and you can apply methods to that inline without variable reassignment.
Assignment does silence the default output, but it does not prevent an object from being further referenced.
($file = $web.GetFile("Pages/default.aspx")).CheckOut()
Of course, it's much more common to either store the return value in a variable and do stuff with it or chain methods/properties/pipes.