Powershell using a regex to create a new hash table object - regex

I'm trying to parse a file that contains blocks of text in this format(there can be 1-n of these):
-----------------------------------------------------------
KB Article Number(s): 2028769, 2072493, 2120979, 2143880, 2163958, 2163980, 980653, 980883, 981155, 981867, 982321, 982850
Language: All (Global)
Platform: x64
Location: (http://foo.bar.com)
Password: foo
-----------------------------------------------------------
The text is coming from an MS Hotfix request email if anyone is interested.
I have the following powershell one liner:
$file | select-string "(?<Name>\w+):(?<Value>.*)" -allmatches | SELECT #{N="Name";E={$_.Matches[0].Groups[1].Value}}, #{N="Value";E={$_.Matches[0].Groups[2].Value}}
This give me a flat set of name value pairs, I would like it to return an array of hashtables with each of the 5 Keys populated.
I'm not sure if the problem is my regex or how I'm accessing the matches (There must be a less verbose way of picking these out).

It's not pretty but I think it will work for you:
$ht = #{}
$file | %{if ($_ -match '(?<Name>[^:]+):(?<Value>.*)') {
$ht.($matches.Name) = $matches.Value.Trim()} `
elseif ($ht.Count) {$ht;$ht.Clear()}}
Using the -match operator is a tad easier because you can use the $matches variable directly without having to go through Select-String's MatchInfo.Matches property. BTW I'm assuming here $file was populated by a call to Get-Content i.e. each line is a string in a string array.

Related

Searching for a matching map name in Powershell

sorry i really dont know how to properly ask this question.
I would like to parse CS:GO Demo files in Powershell, and i would like to retrive the map name from it.
I opening dem files like this:
Get-Content $demo | Select -First 1 | Select-String -Pattern 'de_'
And i get this as response:
HL2DEMO đ5 MatchServer I.
GOTV Demo
de_mirage
csgo
##A g uÔ ~ř˙˙
ą Vđk (8wEÄü€ŢMĐhZăU X#`śh u <zcsgo‚ de_mirageŠ ’sky_dustšGOTV¨ ° ¸  ( 0 ž
I would like to get only the de_mirage as a variable. So if a map changes, then it will be de_dust2 or de_inferno and so on. Does anybody know a solution for this?
Thank you!
When using Get-Content, each line is passed down the pipeline one at a time, unless specifying the -Raw switch. The reason I bring this up is due to your Select cmdlet that you're piping to. When you specified the parameter of -First, with a value of 1, you're only grabbing the first line, and then trying to find the pattern in the first line.
Here's my poor attempt at RegEx:
Get-Content -Path $demo | Where-Object -FilterScript { $_ -match 'de_\w+' }
$Matches[0]
. . .where the $Matches Automatic Variable contains all the matched RegEx patterns (as the name indicates) stored in an array format; where we use the index number to reference the value. This would also work piping to Select-String when searching for a Pattern just like you had done.

Powershell For Loop, Replace, Split, Regex Matches

$i=0;$pnp = pnputil -e;
$matched = [regex]::matches($pnp, ".......................................Lexmark International");
$split = $matched -split (".........inf");
$replace = $split -replace " Driver package provider : Lexmark International","";
$replace1 = $replace -replace " ","`n";write-output $replace1;
foreach ($i in $replace1){;$pnpdel = pnputil -f -d $i;$pnpdel;};
Reg delete "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Drivers\Version-3\Lexmark Universal v2 XL" /f;
net stop spooler;net start spooler;start \\officechicprt5\111W-22E-CPRN-01
As you can hopefully see, my script tries to pull oem*.inf values from a pnpitil -e command. I am trying to get each oem##.inf file to be it's own variable in a For loop. My script is a bit of a mess with all the replaces and splits, but that part seems to get the part of the command that I need. I am having issues with the data in $i. It appears that the script will sometimes work, and sometimes not. I want pnputil -d oem99.inf for each oem# it finds in the pnputil enumeration. What am I doing wrong in my For loop? There has to be a better way... I'm still pretty new to this as you can tell.
Thanks again.
Brent
Leveraging the power in PowerShell we can turn the output of pnputil into an object array that will make it much easier to parse the data you are looking for (since it appears you are looking for something specific).
Each entry is a group of variables with a blank line in-between them. Using that lets turn this data into custom objects.
$rawdata = pnputil -e | Select-Object -Skip 1
$rawdata = $rawdata -join "`r`n" -split "`r`n`r`n"
$entries = $rawdata | ForEach-Object{
$props = $_ -replace ":","=" | ConvertFrom-StringData
new-object -TypeName PSCustomObject -Property $props
}
$rawdata initially contains the text from pnputil -e. We use Select-Object -Skip 1 to remove the "Microsoft PnP Utility" line. Since $rawdata is an array this approach requires that is it one long string so -join "`r`n". Immediately after we split it up into separate array elements of each property group with -split "`r`n`r`n" which splits on the blank line you see in cmd output.
The magic comes from ConvertFrom-StringData which create a hashtable from key value pairs in stings. In needs = to work so we convert the colons as such. With each hashtable that is created we convert it to an object and save it to the variable $entries. $entries will always be an array since it is safe to expect more than one entry.
Sample Entry once converted:
Class : Printers
Driver date and version : 12/03/2014 1.5.0.0
Signer name : Microsoft Windows Hardware Compatibility Publisher
Published name : oem27.inf
Driver package provider : Ricoh
Now we can use PowerShell to filter out exactly what you are looking for!
$entries | Where-Object{$_."Driver package provider" -match "Ricoh"} | Select-Object -ExpandProperty "Published name"
Note that this can also return an array but for me there was only one entry. The output for this was oem27.inf
Then using the information you are actually looking for you can run your other commands.

How can I split and select from an array of filenames in Powershell?

I have a script I wrote in my company for clearing Citrix UPM profiles. Not very complicated, but it generates logs for every user it is run on. Along the format of:
UPMreset-e0155555-20150112-0733
UPMreset-n9978524-20150114-1128
UPMreset-jsmith-20150113-0840
etc.
So I want to grab the folder with all the .txt files, select only the username and count to see if one appears more than a certain number of times. To check for problem children. Putting them into an array is easy enough, but when doing a -split I can't seem to find a regex combination to select only the username. I thought I could just do a ('-')[1], but that doesn't appear to work. Do you have any suggestions?
$arrFiles = Get-Childitem "c:\logs"
$arrFiles | %{ $arrfile = $_ -split ('-'); Write-Host $arrfile[0]}
edit: Included test code for posterity sake.
I'd try something like this:
$Path = 'N:\Folder\*.txt';
Get-ChildItem $Path |
ForEach-Object {
Write-Output $_.BaseName.Split('-')[1];
} |
Group-Object |
Where-Object { $_.Count -gt 1 } |
Sort-Object -Property Name |
Select-Object Name, Count;
To answer the question.
$_ is one of the objects returned by Get-ChildItem. Those objects are not strings. They're .Net objects of type System.IO.DirectoryInfo or System.IO.FileInfo. That means if we use $_, we're referencing the whole object. Worse, neither of those objects has a Split() method, so $_.Split('-') would refer to a function that didn't exist.
BaseName is a property of a FileInfo or DirectoryInfo object. That property contains the name of the file without the path or the extension. Critically, this property is also a String, which does have the Split() method. So using this property does two things: It removes the path name and the extension since we don't care about that and we don't want it to potentially break something (e.g., if someone put a dash in the parent folder's name), and it gives us a String object which we can manipulate with String methods and do things like call the Split function.
Try something like this at the command line:
$x = Get-ChildItem 'N:\Folder\UPMreset-e0155555-20150112-0733.txt';
$x | Get-Member;
You'll get a huge list of Methods (functions) that the object can do and Properties (attribute values) of the object. Name, FullName, BaseName, and Extension are all very common properties to use. You should also see NoteProperties and CodeProperties, which are added by the PowerShell provider to make using them easier (they wouldn't be available in a C# program). The definition tells you how to call the method or what the type of the property is and what you can do with it. You can usually Google and find MSDN documentation for how to use them, although it's not always the easiest way to do things.
Compare the above to this:
$x.BaseName | Get-Member;
You can see that it's a String, that there all kinds of methods like Split, Replace, IndexOf, etc.
Another helpful one is:
$x | Select-Object *;
This returns all the Propety, NoteProperty, and CodeProperty values this object has.
This highlights one of the best ways to learn about what you can do with an object. Pipe it to Get-Member, and you learn the type and any methods or properties that you can access. That, combined with piping something to Select-Object *, can tell you a lot about what you're working with.
What problem were you having with .split('-')[1]?
$filenames = #(
'UPMreset-e0155555-20150112-0733',
'UPMreset-n9978524-20150114-1128',
'UPMreset-jsmith-20150113-0840'
)
$filenames |% {$_.split('-')[1]}
e0155555
n9978524
jsmith
It looks like the filenames are always UPMreset-, followed by the username. So use this:
UPMreset-(.+?)-
and the capture group will contain the username. It's using a lazy quantifier to get anything up to the next dash.
You could also do the split in a calculated property with Group-Object:
$FileNames = Get-ChildItem -Path $LogDir -Filter "*.txt" -Name
$FileNames | Group-Object #{Expression={($_ -split "-")[1]}} | Where-Object {$_.Count -gt 1}

How to get a value from Select-String

I have several files in a folder, those are .xml files.
I want to get a value from those files.
A line in the file, could look like this:
<drives name="Virtual HD ATA Device" deviceid="\\.\PHYSICALDRIVE0" interface="IDE" totaldisksize="49,99">
What i'm trying to do is get the value 49,99 in this case.
I am able to get the line out of the file with:
$Strings = Select-String -Path "XML\*.xml" -Pattern totaldisksize
foreach ($String in $Strings) {
Write-Host "Line is" $String
}
But getting just the value in "" i don't get how. I've also played around with
$Strings.totaldisksize
But no dice.
Thanks in advance.
You can do this in one line as follows:
$(select-string totaldisksize .\XML\*.xml).line -replace '.*totaldisksize="(\d+,\d+)".*','$1'
The Select-String will give you a collection of objects that contains information about the match. The line property is the one you're interested in, so you can pull that directly.
Using the -replace operator, every time the .line property is a match of totaldisksize, you can run the regex on it. The $1 replacement will grab the group in the regex, the group being the part in parentheses (\d+,\d+) which will match one or more digits, followed by a comma, followed by one or more digits.
This will print to screen because by default powershell will print an object to the screen. Because you're only accessing the .line property, that's the only bit that's printed and also only after the replacement has been run.
If you wanted to explicitly use a Write-Host to see the results, or do anything else with them, you could store to a variable as follows:
$sizes = $(select-string totaldisksize .\XML\*.xml).line -replace '.*totaldisksize="(\d+,\d+)".*','$1'
$sizes | % { Write-Host $_ }
The above stores the results to an array, $sizes, and you iterate over it by piping it to the Foreach-Object or %. You can then access the array elements with $_ inside the block.
But.. but.. PowerShell knows XML.
$XMLfile = '<drives name="Virtual HD ATA Device" deviceid="\\.\PHYSICALDRIVE0" interface="IDE" totaldisksize="49,99"></drives>'
$XMLobject = [xml]$XMLfile
$XMLobject.drives.totaldisksize
Output
49,99
Or walk the tree and return the content of "drives":
$XMLfile = #"
<some>
<nested>
<tags>
<drives someOther="stuff" totaldisksize="49,99" freespace="22,33">
</drives>
</tags>
</nested>
</some>
"#
$drives = [xml]$XMLfile | Select-Xml -XPath "//drives" | select -ExpandProperty node
Output
PS> $drives
someOther totaldisksize freespace
--------- ------------- ---------
stuff 49,99 22,33
PS> $drives.freespace
22,33
XPath query of "//drives" = Find all nodes named "drives" anywhere in the XML tree.
Reference: Windows PowerShell Cookbook 3rd Edition (Lee Holmes). Page 930.
I am not sure about powershell but if you prefer using python below is the way of doing it.
import re
data = open('file').read()
item = re.findall('.*totaldisksize="([\d,]+)">', data)
print(item[0])
Output
49,99

How to Find Replace Multiple strings in multiple text files using Powershell

I am new to scripting, and Powershell. I have been doing some study lately and trying to build a script to find/replace text in a bunch of text files (Each text file having code, not more than 4000 lines). However, I would like to keep the FindString and ReplaceString as variables, for there are multiple values, which can in turn be read from a separate csv file.
I have come up with this code, which is functional, but I would like to know if this is the optimal solution for the aforementioned requirement. I would like to keep the FindString and ReplaceString as regular expression compatible in the script, as I would also like to Find/Replace patterns. (I am yet to test it with Regular Expression Pattern)
Sample contents of Input.csv: (Number of objects in csv may vary from 50 to 500)
FindString ReplaceString
AA1A 171PIT9931A
BB1B 171PIT9931B
CC1C 171PIT9931E
DD1D 171PIT9932A
EE1E 171PIT9932B
FF1F 171PIT9932E
GG1G 171PIT9933A
The Code
$Iteration = 0
$FDPATH = 'D:\opt\HMI\Gfilefind_rep'
#& 'D:\usr\fox\wp\bin\tools\fdf_g.exe' $FDPATH\*.fdf
$GraphicsList = Get-ChildItem -Path $FDPATH\*.g | ForEach-Object FullName
$FindReplaceList = Import-Csv -Path $FDPATH\Input.csv
foreach($Graphic in $Graphicslist){
Write-Host "Processing Find Replace on : $Graphic"
foreach($item in $FindReplaceList){
Get-Content $Graphic | ForEach-Object { $_ -replace "$($item.FindString)", "$($item.ReplaceString)" } | Set-Content ($Graphic+".tmp")
Remove-Item $Graphic
Rename-Item ($Graphic+".tmp") $Graphic
$Iteration = $Iteration +1
Write-Host "String Replace Completed for $($item.ReplaceString)"
}
}
I have gone through other posts here in Stackoverflow, and gathered valuable inputs, based on which the code was built. This post from Ivo Bosticky came pretty close to my requirement, but I had to perform the same on a nested foreach loop with Find/Replace Strings as Variables reading from an external source.
To summarize,
I would like to know if the above code can be optimized for
execution, since I feel it takes a long time to execute. (I prefer
not using aliases for now, as I am just starting out, and am fine
with a long and functional script rather than a concise one which is
hard to understand)
I would like to add the number of Iterations being carried out in
the loop. I was able to add the current Iteration number onto the
console, but couldn't figure how to pipe the output of
Measure-Command onto a variable, which could be used in Write-Host
Command. I would also like to display the time taken for code
execution, on completion.
Thanks for the time taken to read this Query. Much appreciate your support!
First of all, unless your replacement string is going to contain newlines (which would change the line boundaries), I would advise getting and setting each $Graphic file's contents only once, and doing all replacements in a single pass. This will also result in fewer file renames and deletions.
Second, it would be (probably marginally) faster to pass $item.FindString and $item.ReplaceString directly to the -replace operator rather than invoking the templating engine to inject the values into string literals.
Third, unless you truly need the output to go directly to the console instead of going to the normal output stream, I would avoid Write-Host. See Write-Host Considered Harmful.
And fourth, you might actually want to remove the Write-Host that gets called for every find and replace, as it may have a fair bit of effect on the overall execution time, depending on how many replacements there are.
You'd end up with something like this:
$timeTaken = (measure-command {
$Iteration = 0
$FDPATH = 'D:\opt\HMI\Gfilefind_rep'
#& 'D:\usr\fox\wp\bin\tools\fdf_g.exe' $FDPATH\*.fdf
$GraphicsList = Get-ChildItem -Path $FDPATH\*.g | ForEach-Object FullName
$FindReplaceList = Import-Csv -Path $FDPATH\Input.csv
foreach($Graphic in $Graphicslist){
Write-Output "Processing Find Replace on : $Graphic"
Get-Content $Graphic | ForEach-Object {
foreach($item in $FindReplaceList){
$_ = $_ -replace $item.FindString, $item.ReplaceString
}
$Iteration += 1
$_
} | Set-Content ($Graphic+".tmp")
Remove-Item $Graphic
Rename-Item ($Graphic+".tmp") $Graphic
}
}).TotalMilliseconds
I haven't tested it but it should run a fair bit faster, plus it will save the elapsed time to a variable.