PowerShell - Why is an array used in this line of code, please? - powershell

I have the following code that looks for a string in multiple files, part of which I found here.
$path = C:\Windows
Set-Location -path $path
$searchWords = 'log.txt'
Foreach ($sw in $searchWords)
{
Get-Childitem -Path $path -Recurse -include "*.txt","*.dll" |
Select-String -Pattern "$sw" |
Select Path,LineNumber,#{n='SearchWord';e={$sw}}
}
The syntax I don't think I understand is this part in the last line:
#{n='SearchWord';e={$sw}}
I'll explain what I think I understand and then ask questions.
# I think means it is an array
n= is shorthand for 'name'
the colon(;) is separating the name of the column and the expression that fills the column.
e= is shorthand for expression
{$sw} - the brackets are necessary here to encapsulate the expression.
Question(s):
Why is an array used to populate this column?
Why must an expression be used and not just the variable '$sw'?
Thanks for the help!

It's not an array but hash table. In the quoted code, a calculated property is used. Usually calculated properties are used to, well, calculate stuff. For example, free disk space can be calculated as percents as per this answer:
#{Name = 'Free';Expression = { "{0:N0}%" -f (($_.FreeSpace/$_.Size) * 100) } }
In the sample you used, calculated property is used to add a label property that contains the search term used on each iteration of the foreach loop.

Thanks to everyone. I didn't know what a calculated property was. #vonPryz helped me understand this. For others trying to understand calculated properties, here are some links which further explain Calculated Properties.
Adam Bertram: Using PowerShell's Calculated Properties
Microsoft Documentation: Calculated Properties

Related

Sort files by number in the end of a file name

I need help with a tricky task I have.
I have a list of *.xml files which have the same ending.
The names below show only the naming convention rule.
EEEEEEEEEE-JJJ-ZZZZZ_DDDDD-XML--0000001629358212.xml
EEEEEEEEEE-JJJ-OOOOOO-XML--0000001533506936.xml
EEEEEEEEEE-JJJ-AAAAAA-XML--0000001572627196.xml
Filename length maybe is different but it's important for me to sort it by this number in the end.
With SQL syntax it would be easier but I need a PS solution).
Sort-Object by LastWriteTime works better than other simple ways - but when it comes to a few files with the same hh:mm PS mixes the order.
At the beginning of the chains of steps that should happen with these files, I remove a time stamps from the beginning of each file name.
I was able to do it with this:
dir *EEEEEEEEEE*.xml | Rename-Item -NewName {$_.name.substring($_.BaseName.IndexOf("EEEEEEEEE"))}
But I'm unable to write something similar for sorting.
Maybe someone can advise how to solve it? Maybe You have more experience with PS Substring.
Thanks in advance.
Read and follow Sort-Object, scrolling down to the -Property parameter:
… The Property parameter's value can be a calculated property. To
create a calculated property, use a hash table.
Valid keys for a hash table are as follows:
expression - <string> or <script block>
ascending or descending - <boolean>
For more information, see about_Calculated_Properties.
Read the Examples section as well. Use
Get-ChildItem -File -Filter "*EEEEEEEEEE*-*.xml" |
Sort-Object -Property #{Expression = { ($_.BaseName -split '-')[-1] }}
Thank you guys for the help.
I used this line below and it worked in my case.
GCI EEEEEEEEEE*.xml | Sort {$_.Name.substring($_.Name.Length - 20,14)}
It reads only the number at the end of the file name and sorts the files in the way I need.
Best regards

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

Fastest Filter Peformance for Array of Objects

I am using the Import-CSV cmdlet to load a list of ~180k items containing 6 columns. Because of the size of this list Where-Object cmdlet is not very performant since each call to it will iterate the entire list. As such I am desperate to find another way to make filtering much quicker. I am always filtering on just one of the columns then updating another column in the resulting filtered list. Is there something I am overlooking that may help? Such as indexing the search column or converting to some other object? Thanks for your ideas.
Iterate over the whole list once, and do each conditional check and relevant substitution on the current item.
$myRows | ForEach-Object -Process {
if ($_.prop1 -eq $condition1) {
$_.prop4 = $newValue
}
if ($_.prop2 -eq $condition2) {
$_.prop6 -eq $someOtherValue
}
}
An even better approach might be to write your own function that encapsulates the transform logic and supports the pipeline. If your columns are always supposed to be the same you can even bind them by property value. Then you'd basically just do:
Import-Csv -Path $myCsv | Convert-MyRow

Powershell Where command not working

I have a text file that contains elements separated by an '=' sign (i.e. color1=red, color2=blue, etc.
I used the import-csv command and provide headers (i.e.
$Import_Cfg = Import-Csv .\Env.cfg -Header Title,Setting -Delimiter =
)
Now this works fine if I want to assign a particular item to another variable if I know the index number and I have used that approach but it won't always work for me because I don't always know what other data will be there.
I thought that by using something like:
$MyColor1 = $Import_Cfg.Setting |where {$_.Title -match "Blue"}
$MyColor2 = $Import_Cfg.Setting |where {$_.Title -match "Red"}
it should work, but I get no returns for either item. When I type in $Import_cfg I can see the entire array (without the "=" signs). If I tell use the command
$MyColor1 = $import_cfg[0].setting
I get the right answer.
Obviously I'm not using colors but a bunch of different items that I need to assign to variables for use elsewhere. Any thoughts on what I'm doing wrong? Everything I've read says that what I have above should work.
Please no flames on why I'm using import-csv vs get-content. I'm sure either will work. This is an approach that I've used and computationally it doesn't matter. If programatically it makes a difference I'm all ears!!!
Thanks for all your help.
The value of the Setting property itself has no Title property.
You need to apply Where before you extract the property value you need (as mentioned in the comments):
$BlueSettings = $Import_Cfg |where {$_.Title -match "Blue"} |Select-Object -ExpandProperty Setting
or, using property enumeration:
$BlueSettings = ($Import_Cfg |where {$_.Title -match "Blue"}).Setting

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.