I'm trying to collect some file properties using PowerShell within Win 2008. To do so, I've created the following script.
# BASIC PARAMETER IF NOT SET
param(
$REGEX='.*'
)
# CURRENT DATE FROM 00H
$DATAATUAL = Get-Date -Hour 0 -Minute 0 -Second 0 -Day 1 # DAY 1 FOR TESTING ONLY
# APPLICATION'S FILE PATH
$PATH = "C:\FTP\import\"
# FILE LIST WITH SELECTED FILES FROM REGULAR EXPRESSION
$FILELIST = Get-ChildItem -Path $PATH | Where-Object { ($_.LastWriteTime -ge $DATAATUAL) -and ($_.Name -cmatch "$REGEX") } | Select-Object -ExpandProperty Name
# OUTPUT IN A SORT OF CSV FORMAT
if ($FILELIST -ne $null) {
Write-Host "name;suffix;fileprocstart;filesize;filewrite"
ForEach ($FILE in $FILELIST) {
# FILE NAME PREFFIX AND SUFFIX
$FILENAME = Select-String -InputObject $FILE -CaseSensitive -Pattern "(^\d+)_($REGEX)"
# FILE TIMESTAMP CONVERTION TO EPOCH UTC-0
$FILEPROCSTART = $FILENAME.Matches.Groups[1].value
$FILEPROCSTART = [datetime]::ParseExact($FILEPROCSTART,"yyyyMMddHHmmss",$null) | Get-Date -UFormat "%s"
$FILEPROCSTART = $($FILEPROCSTART -as [long]) + 10800 # TIMEZONE CORRECTION - ADDED 3H TO BECOME UTC-0
$FILESUFFIX = $FILENAME.Matches.Groups[2].value
# FILE SIZE AND WRITE TIME
$FILESIZE = Get-ChildItem -Path $PATH -Filter $FILE | Select-Object -ExpandProperty Length
$FILEWRITE = Get-ChildItem -Path $PATH -Filter $FILE | Select-Object -ExpandProperty LastWriteTime | Get-Date -UFormat "%s"
# OUTPUT
Write-Host "$FILENAME;$FILESUFFIX;$FILEPROCSTART;$FILESIZE;$FILEWRITE"
}
}
# NO FILES FOUND
Else {
Write-Host "Empty"
}
I can start it like so:
script.ps1 -REGEX 'pattern'
It results in a list like this:
name;suffix;fileprocstart;filesize;filewrite
20220709101112_cacs1_v83.txt;cacs1_v83.txt;1657361472;5;1657397022,47321
20220709101112_cacs1_v83.txt.log;cacs1_v83.txt.log;1657361472;5;1657397041,83271
20220709101112_cacs2_v83.txt;cacs2_v83.txt;1657361472;5;1657397039,70775
20220709101112_cacs3_v83.txt.log;cacs3_v83.txt.log;1657361472;5;1657397038,03647
20220709101112_cakauto4.txt;cakauto4.txt;1657361472;5;1657397037,48906
20220709111112_coord_multicanal.txt.log;coord_multicanal.txt.log;1657365072;5;1657398468,95865
All files are generated on a daily basis and have a format similar to this:
20220709101112_cacs1_v83.txt
20220709101112_cacs1_v83.txt.log
20220709101112_cacs2_v83.txt
20220709101112_cacs3_v83.txt.log
20220709101112_cakauto4.txt
20220709101112_coord_multicanal.txt.log
Basically, the script outputs the file name, file suffix (no timestamp), file timestamp (unix format), file size and Last Write time (unix format), all in a sort of CSV format. It is meant to be started by another system to collect those properties.
It kind of works, but I can't help thinking there must be a better way to do that.
Any considerations on how to improve it?
I'm not sure if I got it right but if I understand this right:
Basically, the script outputs the file name, file suffix, file name timestamp, file size and Last Write time, all in a sort of CSV format. It is meant to be started by another system to collect those properties.
This should be all you need to start with:
$ComputerName = 'RemoteW2K8Computer'
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
Get-ChildItem -Path 'C:\FTP\import' |
Select-Object -Property BaseName,Extension,Length,LastWriteTime,
#{Name = 'FileNameTimeStamp'; Expression = {($_.BaseName -split '_')[0]}}
}
Using #Olaf great tips, I've rewritten the script this way.
param($REGEX='.*')
$DATAATUAL = Get-Date -Hour 0 -Minute 0 -Second 0 -Day 1 # DAY 1 FOR TESTING ONLY
$PATH = "C:\FTP\import"
$TZ = [TimeZoneInfo]::FindSystemTimeZoneById("E. South America Standard Time")
$FILELIST = Get-ChildItem -Path $PATH |
Where-Object { ($_.LastWriteTime -ge $DATAATUAL) -and ($_.Name -cmatch "$REGEX") } |
Select-Object -Property Name,Length,
#{Name = 'Suffix'; Expression = { ($_.Name -split '_',2)[1] } },
#{Name = 'ProcStart'; Expression = {
$PROCSTART = ($_.Name -split '_')[0];
$PROCSTART = [datetime]::ParseExact($PROCSTART,"yyyyMMddHHmmss",$null);
[TimeZoneInfo]::ConvertTimeToUtc($PROCSTART, $TZ) | Get-Date -UFormat "%s";
} },
#{Name = 'FileWrite' ; Expression = {
$WRITETIME = $_.LastWriteTime;
[TimeZoneInfo]::ConvertTimeToUtc($WRITETIME) | Get-Date -UFormat "%s";
} }
if ($FILELIST -ne $null) {
Write-Host "name;suffix;procstart;filesize;filewrite"
# $FILELIST | ConvertTo-Csv -Delimiter ';' -NoTypeInformation
ForEach ($FILE in $FILELIST) {
$FILENAME = $FILE.Name
$FILESUFFIX = $FILE.Suffix
$FILESIZE = $FILE.Length
$FILEPROCSTART = $FILE.ProcStart
$FILEWRITE = $FILE.FileWrite
Write-Host "$FILENAME;$FILESUFFIX;$FILESIZE;$FILEPROCSTART;$FILEWRITE"
}
}
Else {
Write-Host "Empty"
}
As said, the output is in a CSV format.
name;suffix;procstart;filesize;filewrite
20220709101112_cacs1_v83.txt;cacs1_v83.txt;5;1657361472;1657397022,47321
20220709101112_cacs1_v83.txt.log;cacs1_v83.txt.log;5;1657361472;1657397041,83271
If I use ConvertTo-Csv (much simpler) instead of ForEach, the output would also be a CSV.
However, it places quotation marks that mess up other conversions to JSON elsewhere (maybe I can improve that later).
# $FILELIST | ConvertTo-Csv -Delimiter ';' -NoTypeInformation
"Name";"Length";"Suffix";"ProcStart";"FileWrite"
"20220709101112_cacs1_v83.txt";"5";"cacs1_v83.txt";"1657361472";"1657397022,47321"
"20220709101112_cacs1_v83.txt.log";"5";"cacs1_v83.txt.log";"1657361472";"1657397041,83271"
The other system convert it to this (I can't use ConvertTo-Json in Win2008 :-/):
{
"\"Name\"": "\"20220709101112_cacs1_v83.txt\"",
"\"Length\"": "\"5\"",
"\"Suffix\"": "\"cacs1_v83.txt\"",
"\"ProcStart\"": "\"1657361472\"",
"\"FileWrite\"": "\"1657397022,47321\""
}
Therefore, I find that writing the values with ForEach gives me a cleaner output.
Also, for fun, measuring with Measure-Command, I found that the new script is a bit faster.
The previous script takes about 24 milliseconds to complete while using a small dataset.
Now, the new one takes about 13 milliseconds with the same dataset.
All in all, a small, but good improvement, I guess.
Cheers to #Olaf for pointing to a better script and for his patience. Thank you.
Related
I have a folder with multiple files and need to rename them to a string inside of the folder. The string is the date of interaction.
Currently the files are named as
AUDIT-1.log
AUDIT-2.log
AUDIT-3.log
ect..
I need to have them as
AUDIT-11-08-22-1.log
AUDIT-11-07-22-2.log
AUDIT-11-08-22-3.log
The issue I am having with the current iteration of the code, the dates of all files are collected and it attempts to rename the file with all dates
EXAMPLE:
NewName: 11-08-22 11-07-22 11-06-22 11-09-22 11-08-22 11-07-22 11-06-22 11-09-22-1.LOG
OldName: C:\TestTemp\AUDIT-2.LOG
There is only one date in each file.
The following is my current code:
$dir ="C:\TestTemp"
$files = Get-ChildItem -Path "$dir\*.log"
$RegexDate = '\d\d\/\d\d\/\d\d'
Measure-Command{
$file_map = #()
foreach ($file in $files) {
$DateName= Get-Content $files |
Select-String $RegexDate |
foreach-object { $_.Matches.Value } |
Select-Object
$NewDateName= $DateName.replace('/','-')
$b = 1
$file_map += #{
OldName = $file.Fullname
NewName = "$NewDateName-$b.LOG" -f $(Get-Content $file.Fullname | Select-Object $NewDateName.Fullname)
}
}
$file_map | ForEach-Object { Rename-Item -Path $_.OldName -NewName $_.NewName }
}
As pointed out in the comments by Santiago Squarzon, the immediate fix is to swap $files, for $file. For code brevity, here's a single pipeline solution you can implement to attain the same results:
Select-String -Path "$dir\*.log" -Pattern '(\d+\/){2}\d+' |
Rename-Item -NewName {
$_.FileName -replace '-', "-$($_.Matches.Value.Replace('/','-'))-"
} -WhatIf
Again, as mentioned in the comments, the use of Select-String allows the reading of file(s) presenting the opportunity to pipe directly into Rename-Item via parameter binding through its Path property. So, using a scriptblock for the new name replacement we're essentially inserting the value found from it's pattern matched into the file name where - would have been.
The -WhatIf safety/common parameter can be removed when you've dictated those are the results you are after.
This will rename the files using their last write time.
If the files were already in that format, they will not be renamed.
There is a hashtable to track the increment of the suffix for the date of the file. This way the files can be organized by date.
$dir = "C:\TestTemp"
$files = Get-ChildItem -Path "$dir\*.log"
#Hashtable to track the suffix for the files
[hashtable]$dateTracking = #{}
#Using padding to format the suffix with two digits, in case there more then 9 files
#incrase it if you have more then 99 files per day increase padding
$suffixPadding = '{0:d2}'
foreach ($file in $files) {
#Don't rename files that were already renamed
if ($file.Name -notmatch "AUDIT-\d{2}-\d{2}-\d{2}-\d{2}\.log") {
$date = $file.LastWriteTime.ToString("MM-yy-dd")
#If the date is not entered in the hashtable add it with suffix 01
if (-not $dateTracking.ContainsKey($date)) {
$dateTracking.Add($date, $suffixPadding -f 1)
}
#Else increment suffix
else {
$dateTracking[$date] = $suffixPadding -f ([int]$dateTracking[$date] + 1)
}
#Here we use the date in the name of the file and getting the suffix from the hashtable
Write-Host "Renaming $($file.Name) to AUDIT-$date-$($dateTracking[$date]).log"
Rename-Item -Path $file -NewName "AUDIT-$date-$($dateTracking[$date]).log"
}
}
I write a Powershell script and regex to search two configs text files to find matches for Management Vlan. For example, each text file has two Management vlan configured as below:
Config1.txt
123 MGMT_123_VLAN
234 MGMT_VLAN_234
Config2.txt
890 MGMT_VLAN_890
125 MGMT_VLAN_USERS
Below is my script. It has several problems.
First, if I ran the script with the $Mgmt_vlan = Select-String -Path $File -Pattern $String -AllMatches then the screen output shows the expected four (4) Mgmt vlan, but in the CSV file output shows as follow
Filename Mgmt_vlan
Config1.txt System.Object[]
Config2.txt System.Object[]
I ran the script the output on the console screen shows exactly four (4) Management vlans that I expected, but in the CSV file it did not. It shows only these vlans
Second, if I ran the script with $Mgmt_vlan = Select-String -Path $File -Pattern $String | Select -First 1
Then the CSV shows as follows:
Filename Mgmt_vlan
Config1.txt 123 MGMT_123_VLAN
Config2.txt 890 MGMT_VLAN_890
The second method Select -First 1 appears to select only the first match in the file. I tried to change it to Select -First 2 and then CSV shows column Mgmt_Vlan as System.Object[].
The result output to the screen shows exactly four(4) Mgmt Vlans as expected.
$folder = "c:\config_folder"
$files = Get-childitem $folder\*.txt
Function find_management_vlan($Text)
{
$Vlan = #()
foreach($file in files) {
Mgmt_Vlan = Select-String -Path $File -Pattern $Text -AllMatches
if($Mgmt_Vlan) # if there is a match
{
$Vlan += New-Object -PSObject -Property #{'Filename' = $File; 'Mgmt_vlan' = $Mgmt_vlan}
$Vlan | Select 'Filename', 'Mgmt_vlan' | export-csv C:\documents\Mgmt_vlan.csv
$Mgmt_Vlan # test to see if it shows correct matches on screen and yes it did
}
else
{
$Vlan += New-Object -PSObject -Property #{'Filename' = $File; 'Mgmt_vlan' = "Mgmt Vlan Not Found"}
$Vlan | Select 'Filename', 'Mgmt_vlan' | Export-CSV C:\Documents\Mgmt_vlan.csv
}
}
}
find_management_vlan "^\d{1,3}\s.MGMT_"
Regex correction
First of all, there are a lot of mistakes in this code.
So this is probably not code that you actually used.
Secondly, that pattern will not match your strings, because if you use "^\d{1,3}\s.MGMT_" you will match 1-3 numbers, any whitespace character (equal to [\r\n\t\f\v ]), any character (except for line terminators) and MGMT_ chars and anything after that. So not really what you want. So in your case you can use for example this: ^\d{1,3}\sMGMT_ or with \s+ for more than one match.
Code Correction
Now back to your code... You create array $Vlan, that's ok.
After that, you tried to get all strings (in your case 2 strings from every file in your directory) and you create PSObject with two complex objects. One is FileInfo from System.IO and second one is an array of strings (String[]) from System. Inside the Export-Csv function .ToString() is called on every property of the object being processed. If you call .ToString() on an array (i.e. Mgmt_vlan) you will get "System.Object[]", as per default implementation. So you must have a collection of "flat" objects if you want to make a csv from it.
Second big mistake is creating a function with more than one responsibility. In your case your function is responsible for gathering data and after that for exporting data. That's a big no no. So repair your code and move that Export somewhere else. You can use for example something like this (i used get-content, because I like it more, but you can use whatever you want to get your string collection.
function Get-ManagementVlans($pattern, $files)
{
$Vlans = #()
foreach ($file in $files)
{
$matches = (Get-Content $file.FullName -Encoding UTF8).Where({$_ -imatch $pattern})
if ($matches)
{
$Vlans += $matches | % { New-Object -TypeName PSObject -Property #{'Filename' = $File; 'Mgmt_vlan' = $_.Trim()} }
}
else
{
$Vlans += New-Object -TypeName PSObject -Property #{'Filename' = $File; 'Mgmt_vlan' = "Mgmt Vlan Not Found"}
}
}
return $Vlans
}
function Export-ManagementVlans($path, $data)
{
#do something...
$data | Select Filename,Mgmt_vlan | Export-Csv "$path\Mgmt_vlan.csv" -Encoding UTF8 -NoTypeInformation
}
$folder = "C:\temp\soHelp"
$files = dir "$folder\*.txt"
$Vlans = Get-ManagementVlans -pattern "^\d{1,3}\sMGMT_" -files $files
$Vlans
Export-ManagementVlans -path $folder -data $Vlans```
Summary
But in my opinion in this case is overprogramming to create something like you did. You can easily do it in oneliner (but you didn't have information if the file doesn't include anything). The power of powershell is this:
$pattern = "^\d{1,3}\s+MGMT_"
$path = "C:\temp\soHelp\"
dir $path -Filter *.txt -File | Get-Content -Encoding UTF8 | ? {$_ -imatch $pattern} | select #{l="FileName";e={$_.PSChildName}},#{l="Mgmt_vlan";e={$_}} | Export-Csv -Path "$path\Report.csv" -Encoding UTF8 -NoTypeInformation
or with Select-String:
dir $path -Filter *.txt -File | Select-String -Pattern $pattern -AllMatches | select FileName,#{l="Mgmt_vlan";e={$_.Line}} | Export-Csv -Path "$path\Report.csv" -Encoding UTF8 -NoTypeInformation
I am simply trying to create a powershell script that will change number values in a set of text files. The data in the text files are separated by semi-colons. The values I want to change are always the 2nd and 3rd tokens on each line of the text file.
An example of a line in one of the files:
"Bridge_Asphalt_F";202498.396728;1104.362183;9.721280;0.000000;0.000000;1.000000;-1.299559;
I want to allow the user of the script to enter values to be added to(or subtracted from) the 2nd and 3rd values in all the lines of all the text files in the current directory.
I have a very basic understanding of scripting, but I've been searching around for hours trying to wrap my head around how this would be accomplished.
This is what I have so far but I'm sure I'm getting a few things wrong:
$east = Read-Host 'Easting?'
$north = Read-Host 'Northing?'
Get-ChildItem *.txt |
Foreach-Object {
$c = ($_ | Get-Content)
$c = $c -replace $regexB,$regexB+$east
$c = $c -replace $regexC,$regexC+$north
[IO.File]::WriteAllText($_.FullName, ($c -join "`r`n"))
}
The values determine an object's location on a map (for a game) and I want to be able to move all objects on the entire map by a certain distance on both x and y axis.
Assuming that each line in the file has the same format as your example, then you can treat the file as a CSV and update it like this:
$offset2 = 100
$offset3 = 100
Import-Csv .\data.txt -Delimiter ';' -Header (1 .. 9) |
ForEach-Object {
$_.2 = ([double]$_.2) + $offset2
$_.3 = ([double]$_.3) + $offset3
$_
} | ConvertTo-Csv -NoTypeInformation -Delimiter ';' |
Select-Object -Skip 1 |
Add-Content .\updated.txt
Note:
ConvertTo-Csv surrounds each item with quotes, so you end up with something like this:
"Bridge_Asphalt_F";"202198.396728";"1104.362183";"9.721280";"0.000000";"0.000000";"1.000000";"-1.299559"
This may cause problems if this isn't expected by your game. If so, then some more processing on the pipeline could be done to strip it out.
Also, I've had issues in the past with trying to import and export to the same CSV file, hence my code outputs to a different file. Test it yourself and if it works with the same file, great, otherwise, copy my example, then add a line to replace the existing file with the new one (e.g. using Move-Item).
I guess that's what you need:
cls
cd C:\Users\dandraka\Desktop\test #or whereever
$eastStr = Read-Host 'Easting?'
$northStr = Read-Host 'Northing?'
# convert input to number
$east = [decimal]::Parse($eastStr)
$north = [decimal]::Parse($northStr)
# loop through files
$files = Get-ChildItem *.txt
$files | Foreach-Object {
$fileName = $_.FullName # just for clarity
Write-Host $fileName
$newLines = New-Object System.Collections.ArrayList
# loop through lines of each file
$lines = Get-Content -Path $fileName
$lines | ForEach-Object {
$line = $_.ToString() # just for clarity
$lineItems = $line -split ';'
$pointName = $lineItems[0]
$latitudeStr = $lineItems[1]
$longitudeStr = $lineItems[2]
# convert to number
$latitude = [decimal]::Parse($latitudeStr)
$longitude = [decimal]::Parse($longitudeStr)
Write-Host "$pointName latitude $latitude , longitude $longitude"
# do the math
$newLatitude = $latitude + $north
$newLongitude = $longitude + $east
Write-Host "$pointName new latitude $newLatitude , new longitude $newLongitude"
# recontruct the line
$newLine = ""
for($i=0; $i -lt $lineItems.Count; $i++) {
if ($i -eq 1) {
$newLine += "$newLatitude;"
continue
}
if ($i -eq 2) {
$newLine += "$newLongitude;"
continue
}
# this if fixes a small bug, without it there are two ; at the end of each line
if ($lineItems[$i].Length -gt 0) {
$newLine += "$($lineItems[$i]);"
}
}
Write-Host "Old line $line"
Write-Host "New line $newLine"
$newLines.Add($newLine) | Out-Null
}
# write file
$newFilename = $fileName.Replace(".txt", ".dat")
[System.IO.File]::WriteAllLines($newFilename, $newLines)
Write-Host "File $newFilename written"
}
A few things to note here:
As you mention that you're starting with powershell, I've written the code more verbose than I would for, say, a seasoned developer. But that actually doesn't hurt.
For the same reason, the code is sub-optimal on purpose (makes for easier to read code). But for better performance and large files (say, a few 10s of MB or more) you need to do things differently, e.g. avoid strings and use string builder instead.
Obviously you can comment out all the Write-Host statements, they're there just to help you make sure the code is working properly.
Hope that helps!
Jim
If your game cannot handle the quoted coordinate values you get when using ConvertTo-Csv or Export-Csv, this should update the values while leaving the quotes off:
$eastOffset = 100
$northOffset = -200
(Get-Content 'D:\coordinates.txt') | ForEach-Object {
$fields = $_ -split ';'
[double]$fields[1] += $eastOffset
[double]$fields[2] += $northOffset
# write the updated stuff to file
Add-Content -Path 'D:\newcoordinates.txt' -Value ($fields -join ';')
}
this content
"Bridge_Asphalt_F";202498.396728;1104.362183;9.721280;0.000000;0.000000;1.000000;-1.299559;
"Road_Asphalt_F";202123.396728;1104.362456;9.721280;0.000000;0.000000;1.000000;-1.299559;
would become
"Bridge_Asphalt_F";202598.396728;904.362183;9.721280;0.000000;0.000000;1.000000;-1.299559;
"Road_Asphalt_F";202223.396728;904.362456;9.721280;0.000000;0.000000;1.000000;-1.299559;
I have a huge ACS.txt report created in Kiwi, and I'd like to:
ID particular lines which have a set string "RADIUS Accounting" then ....
...from those lines take two values "User-ID=XXXXXXXX#domain.com" and "MAC=xx-xx-xx-xx-xx-xx-xx-xx", then output that in a txt.
This is what I have right now
Get-Content C:ACS.txt | ForEach-Object {
$null = $_ -match "RADIUS Accounting",\s.*User-Name=(?<user>[0-9]+#domain.com).*Calling-Station-ID=(?<mac>([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})).*"; $matches.user; $matches.mac
}
I think it's giving me what I want, it's just in one long list, rather than user/mac per line.
What about using Select-String and iterate the found (sub)matches
building a new PSCustomObject you can export or view
$UserMac = Select-String -Path C:ACS.txt -Pattern "RADIUS Accounting.*User-Name=([0-9]+#domain.com).*Calling-Station-ID=(([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}))" |
ForEach-Object{
[PSCustomObject]#{
User = $matches.Groups[1].Value
Mac = $matches.Groups[2].Value
}
}
# $UserMac
$UserMac | Out-Gridview
# $UserMac | Export-Csv .\UserMac.csv -NoType
I have a fixed width file with records in a format as follows
DDEDM2018890 19960730015000010000
DDETPL015000 20150515015005010000
DDETPL015010 20150515015003010000
DDETPL015020 20150515015002010000
DDETPL015030 20150515015005010000
DDETPL015040 20150515015000010000
the first 3 characters identify the record type, in the above example all records are of type DDE but there are also lines of a different type in the file.
the following regular expression with named capture groups parses the relevant information from each record for my purpose (notice it also filters down to DDE record types:
DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3})
play with this regex on this excellent online parser
I have written a script that uses the Get-Content, ForEach-Object and Select-Object cmdlets to convert the fixed width file into a csv file.
I wonder if I could replace the Get-Content and ForEach-Object cmdlets by a single Select-String cmdlet?
#this powershell script reads fixed width file and generates a csv file of the relevant & converted values
#Prepare HashSet object for Select-Object to convert CategoryCode and append with CategoryId
$Category = #{
Name = "Category"
Expression = {
$cat = switch($_.CategoryCode)
{
"50"{"A"}
"54"{"C"}
"60"{"F"}
"66"{"I"}
"74"{"M"}
"88"{"T"}
}
$cat+$_.CategoryId
}
}
gc "C:\Path\To\File.txt" | % {
if($_ -match "DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3}).*$")
{
#$matches is a hashset of named capture groups, convert to object to allow Select-Object to handle hashset elements as object properties
[PSCustomObject]$matches
}
} | select Database, $Category, Length #| export-csv "AnalysisLengths.csv" -NoTypeInformation
Before I finalized the script, I was trying to use the Select-String cmdlet but could not figure out how to use it, I believe it can achieve the same result in a more eloquent way... this is what I had:
##Could this be completed with just the Select-String commandlet instead of Get-Content+ForEach+Select-Object?
Select-String -Path "C:\Path\To\File.txt" `
-Pattern "DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3})" `
| Select-Object -ExpandProperty Matches
Using -ExpandProperty should convert the Microsoft.PowerShell.Commands.MatchInfo Matches property into the actual System.Text.RegularExpressions.Match objects for each line...
see also Powershell Select-Object vs ForEach on Select-String results
Here is one way (I'am not so proud of it)
Select-String -Path "C:\Path\To\File.txt" -Pattern "DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3})" | %{New-Object -TypeName PSObject -Property #{Database=$_.matches.groups[1];CategoryCode=$_.matches.groups[2];CategoryId=$_.matches.groups[3];Length=$_.matches.groups[4]}} | export-csv "C:\Path\To\File.csv"
I don't know why you have limited your question to Select-String cmdlet. If you had included the switch statement, then, I'd answer to you: YES! It's possible!
And I'd present to you this simple and short PowerShell code:
$(switch -Regex -File $fileIN{$patt{[pscustomobject]$matches|select * -ExcludeProperty 0}})|epcsv $fileCSV`
where $fileIN is the input file, $fileCSV is CSV file you wanna create, and $patt is the pattern you have in your OP:
$patt='DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3})'`
The switch statement is very powerful.
While Select-String can combine Get-Content and pattern matching, you still need a loop for constructing your custom objects. You could stick with what you have, although I'd suggest a couple modifications. Replace the switch statement with a hashtable and make the nested if a Where-Object filter:
$categories = #{
'50' = 'A'
'54' = 'C'
'60' = 'F'
'66' = 'I'
'74' = 'M'
'88' = 'T'
}
$category = #{
Name = 'Category'
Expression = { $categories[$_.CategoryCode] + $_.CategoryId }
}
$pattern = 'DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3})'
Get-Content 'C:\path\to\file.txt' |
? { $_ -match $pattern } |
% { [PSCustomObject]$matches } |
select Database, $category, Length |
Export-Csv 'C:\path\to\output.csv' -NoType
Or you could go with #JPBlanc's suggestion (again with some slight modifications):
$category = #{
'50' = 'A'
'54' = 'C'
'60' = 'F'
'66' = 'I'
'74' = 'M'
'88' = 'T'
}
$pattern = "DDE(?<Database>\w{3})\d{2}(?<CategoryCode>\d{2})(?<CategoryId>\d{1})\d\s+\d{8}\d{3}(?<Length>\d{3})"
Select-String -Path 'C:\path\to\file.txt' -Pattern $pattern | % {
New-Object -TypeName PSObject -Property #{
Database = $_.Matches.Groups[1].Value
Category = $category[$_.Matches.Groups[2].Value] + $_.Matches.Groups[3].Value
Length = $_.Matches.Groups[4].Value
}
} | Export-Csv 'C:\path\to\output.csv' -NoType
The latter will give you slightly better performance, although not too much (execution times were 2:35 vs 2:50 for a 120 MB input file on my test box).