Powershell to match the current line and next line then out-file - powershell

I'm trying to extract the data whereby:
line 1 = Report ID + Line 2 = "Machine no" + Line 3 = OFFLINE
Then Out-File to a new file.
Sample data
Report ID page1
Machine no 1234
OTHERS
12
offline
12
OTHERS
23
offline
37
OTHERS
89
offline
65
The result I'm looking for look something like the below after processing:
Report ID page 4
Machine no 1234
offline
12
offline
37
offline
65

You can use the Select-String cmdlet with the -Context Parameter to search through a file and then select how many lines of contextual info you want to get back from your search.
For instance, if we take your input file and store it in a variable called $input like so:
$inputFile= Get-Content C:\Path\To\YourFile.txt
$inputFile| Select-string 'machine no'
>Machine no 1234
We can then find matches for the phrase 'offline' with this:
$inputFile| Select-String offline -Context 0,1
This states that I want you to search for the word 'offline' and give me zero lines proceeding it, and one line after it, giving us this output:
> offline
12
> offline
37
> offline
65
We can put this all together to build this and generate a new output file that would look like this.
$out= ''
$out += $inputFile| Select-string 'machine no'
$out += "`n"
$out += $inputFile| Select-String offline -Context 0,1 | ForEach-Object {$_.Context.DisplayPostContext}
#Resulting file would look this, just the name of the machine and then the number of each offline...uh, whatever it is.
Machine no 1234
12 37 65
If I were you, I'd adapt this flow to make PowerShell objects and properties instead, like this:
$Report = [pscustomobject]#{ID='';MachineNo='';OfflineMembers=#()}
$Report.ID = ($inputFile | select-string page -Context 0 ).ToString().Split('page')[-1]
$Report.MachineNo = ($inputFile | Select-string 'machine no').ToString().Trim().Split()[-1]
$Report.OfflineMembers = $inputFile | Select-String offline -Context 0,1 | ForEach-Object {
[pscustomobject]#{Value='Offline';ID=$_.Context.DisplayPostContext.Trim()}
}
>$Report
ID MachineNo OfflineMembers
-- --------- --------------
1 1234 {#{Value=Offline; ID=12}, #{Value=Offline; ID=37}, #{Value=Offline; ID=65}}
$Report.OfflineMembers
Value ID
----- --
Offline 12
Offline 37
Offline 65

Related

Adding two broken rows using powershell

I have a file which has header at first row and then other data at remaining rows. I want to check whether all rows has equal no of data with header.
For example: if header has 10 count then I want all my remaining rows to have 10 data each so there will be no error while loading the data.
Suppose in line 5 and 6 there are only 5 data each.So,i want to combine these two rows in such case.
My expected output is(Row 5 has the merged data)
There may such breakable data in many rows of the file.So, i just want to scan whole file and will merge the two rows when such cases are seen.
So, I tried using:
$splitway=' '
$firstLine = Get-Content -Path $filepath -TotalCount 1
$firstrowheader=$firstLine.split($splitway,System.StringSplitOptions]::RemoveEmptyEntries)
$requireddataineachrow=$firstrowheader.Count
echo $requireddataineachrow
The above code will give me 10 since my header is having 10 data.
For ($i = 1; $i -lt $totalrows; $i++) {
$singleline=Get-Content $filepath| Select -Index $i
$singlelinesplit=$singleline.split($splitway,[System.StringSplitOptions]::RemoveEmptyEntries)
if($singlelinesplit.Count -lt $requireddataineachrow){
$curr=Get-Content $filepath| Select -Index $i
$next=Get-Content $filepath| Select -Index $i+1
Write-Host (-join($curr, " ", $next))
}
echo $singlelinesplit.Count
}
I tested using Write-Host (-join($curr, " ", $next)) to join two lines but it's not giving the correct output.
echo $singlelinesplit.Count is showing correct result:
My whole data is:
billing summary_id offer_id vendor_id import_v system_ha rand_dat mand_no sad_no cad_no
11 23 44 77 88 99 100 11 12 500
1111 2333 4444 6666 7777777 8888888888 8888888888888 9999999999 1111111111111 2000000000
33333 444444 As per new account ddddddd gggggggggggg wwwwwwwwwww bbbbbbbbbbb qqqqqqqqqq rrrrrrrrr 5555555
22 33 44 55 666<CR>
42 65 66 55 244
11 23 44 76 88 99 100 11 12 500
1111 2333 new document 664466 7777777 8888888888 8888888888888 9999999999 111111144111 200055000
My whole code if needed is:
cls
$filepath='D:\test.txt'
$splitway=' '
$totalrows=#(Get-Content $filepath).Length
write-host $totalrows.gettype()
$firstLine = Get-Content -Path $filepath -TotalCount 1
$firstrowheader=$firstLine.split($splitway,[System.StringSplitOptions]::RemoveEmptyEntries)
$requireddataineachrow=$firstrowheader.Count
For ($i = 1; $i -lt $totalrows; $i++) {
$singleline=Get-Content $filepath| Select -Index $i
$singlelinesplit=$singleline.split($splitway,[System.StringSplitOptions]::RemoveEmptyEntries)
if($singlelinesplit.Count -lt $requireddataineachrow){
$curr=Get-Content $filepath| Select -Index $i
$next=Get-Content $filepath| Select -Index $i+1
Write-Host (-join($curr, " ", $next))
}
echo $singlelinesplit.Count
}
Update: It seems that instances of string <CR> are a verbatim part of your input file, in which case the following solution should suffice:
(Get-Content -Raw sample.txt) -replace '<CR>\s*', ' ' | Set-Content sample.txt
Here's a solution that makes the following assumptions:
<CR> is just a placeholder to help visualize an actual newline in the input file.
Only data rows with fewer columns than the header row require fixing (as Mathias points out, your data is ambiguous, because a column value such as As per new account technically comprises three values, due to its embedded spaces).
Such a data row can blindly be joined with the subsequent line (only) to form a complete data row.
# Create a sample file.
#'
billing summary_id offer_id vendor_id import_v system_ha rand_dat mand_no sad_no cad_no
11 23 44 77 88 99 100 11 12 500
1111 2333 4444 6666 7777777 8888888888 8888888888888 9999999999 1111111111111 2000000000
33333 444444 As per new account ddddddd gggggggggggg wwwwwwwwwww bbbbbbbbbbb qqqqqqqqqq rrrrrrrrr 5555555
22 33 44 55 666
42 65 66 55 244
11 23 44 76 88 99 100 11 12 500
1111 2333 new document 664466 7777777 8888888888 8888888888888 9999999999 111111144111 200055000
'# > sample.txt
# Read the file into the header row and an array of data rows.
$headerRow, $dataRows = Get-Content sample.txt
# Determine the number of whitespace-separated columns.
$columnCount = (-split $headerRow).Count
# Process all data rows and save the results back to the input file:
# Whenever a data row with fewer columns is encountered,
# join it with the next row.
$headerRow | Set-Content sample.txt
$joinWithNext = $false
$dataRows |
ForEach-Object {
if ($joinWithNext) {
$partialRow + ' ' + $_
$joinWithNext = $false
}
elseif ((-split $_).Count -lt $columnCount) {
$partialRow = $_
$joinWithNext = $true
}
else {
$_
}
} | Add-Content sample.txt

Office 365 Powershell - Formatting the output of a foreach-object loop

Good evening all,
Still pretty new to Powershell but I've been trying to get some scripts together to make some of the reporting easier for a lot of the information that we need to pull from our clients' Office 365 tenants.
The script already does what I need it to (grab the subscriptions from the tenant, grab the friendly name of the subscription from my CSV, then write to the console), however my question is about the formatting: I would like to format this output in a table with the column headers Subscriptions, Active, Suspended, Assigned.
$sku = Get-MsolAccountSku | select-object skupartnumber,ActiveUnits,suspendedUnits,ConsumedUnits | sort-object -property skupartnumber
$skudata = import-csv -Header friendlyname,skupartnumber "C:\PShell\SKUs.csv" | where-object {$sku.skupartnumber -eq $_.skupartnumber} | sort-object -property skupartnumber
$skudata | foreach-object {$n = 0}{write-host $skudata.friendlyname[$n], $sku.ActiveUnits[$n], $sku.SuspendedUnits[$n], $sku.ConsumedUnits[$n]; $n = $n + 1}
## Output:
POWER BI (FREE) 1000000 0 1
MICROSOFT TEAMS EXPLORATORY 100 0 6
Here's the script and output, I'm using write-host right now because this formats the data in the same way that we already want it and I can easily copy it out of the console and in to our tickets. When I use write-output, however, it puts each cell on its own line and I can't seem to figure out how to pipe this in to format-table .
$skudata | foreach-object {$n = 0}{write-output $skudata.friendlyname[$n], $sku.ActiveUnits[$n], $sku.SuspendedUnits[$n], $sku.ConsumedUnits[$n]; $n = $n + 1}
# Output:
POWER BI (FREE)
1000000
0
1
MICROSOFT TEAMS EXPLORATORY
100
0
6
I'm already able to get everything I need using the below hash table and simple script, the only problem is that it can't pull the common subscription name and Microsoft's skupartnumber identifier is the best option which isn't always very descriptive. I'm sure there's some very simple solution I'm just missing, but if anyone could point me in the right direction for either solution I've tried it would be greatly appreciated!
$subs = #{Label="Subscription"; Expression={$_.skupartnumber}; Alignment = "right";}
$active = #{Label="Active"; Expression={$_.ActiveUnits}; Alignment = "right"}
$assigned = #{Label="Assigned"; Expression={$_.ConsumedUnits}; Alignment = "right"}
$suspended = #{Label="Suspended"; Expression={$_.SuspendedUnits}; Alignment = "right"}
Get-MsolAccountSku | Format-Table $subs, $active, $suspended, $assigned -autosize
# Output:
Subscription Active Suspended Assigned
------------ ------ --------- --------
POWER_BI_STANDARD 1000000 0 1
TEAMS_EXPLORATORY 100 0 6
PowerShell is an object oriented language and what you need is to gather the info you want in objects for further processing or output.
Try
$result = for ($n = 0; $n -lt $skudata.Count; $n++) {
# output the data as PSObject that gets collected in variable $result
[PsCustomObject]#{
Subscription = $skudata.friendlyname[$n]
Active = $sku.ActiveUnits[$n]
Suspended = $sku.SuspendedUnits[$n]
Assigned = $sku.ConsumedUnits[$n]
}
}
# output on screen
$result | Format-Table -AutoSize
# output to CSV
$result | Export-Csv -Path "C:\PShell\SkuUsage.csv" -NoTypeInformation

re-arrange and combine powershell custom objects

I have a system that currently reads data from a CSV file produced by a separate system that is going to be replaced.
The imported CSV file looks like this
PS> Import-Csv .\SalesValues.csv
Sale Values AA BB
----------- -- --
10 6 5
5 3 4
3 1 9
To replace this process I hope to produce an object that looks identical to the CSV above, but I do not want to continue to use a CSV file.
I already have a script that reads data in from our database and extracts the data that I need to use. I'll not detail the fairly long script that preceeds this point but in effect it looks like this:
$SQLData = Custom-SQLFunction "SELECT * FROM SALES_DATA WHERE LIST_ID = $LISTID"
$SQLData will contain ~5000+ DataRow objects that I need to query.
One of those DataRow object looks something like this:
lead_id : 123456789
entry_date : 26/10/2018 16:51:16
modify_date : 01/11/2018 01:00:02
status : WRONG
user : mrexample
vendor_lead_code : TH1S15L0NGC0D3
source_id : A543212
list_id : 333004
list_name : AA Some Text
gmt_offset_now : 0.00
SaleValue : 10
list_name is going to be prefixed with AA or BB.
SaleValue can be any integer 3 and up, however realistically extremely unlikely to be higher than 100 (as this is a monthly donation) and will be one of 3,5,10 in the vast majority of occurrences.
I already have script that takes the content of list_name, creates and populates the data I need to use into two separate psobjects ($AASalesValues and $BBSalesValues) that collates the total numbers of 'SaleValue' across the data set.
Because I cannot reliably anticipate the value of any SaleValue I have to dynamically create the psobjects properties like this
foreach ($record in $SQLData) {
if ($record.list_name -match "BB") {
if ($record.SaleValue -gt 0) {
if ($BBSalesValues | Get-Member -Name $($record.SaleValue) -MemberType Properties) {
$BBSalesValues.$($record.SaleValue) = $BBSalesValues.$($record.SaleValue)+1
} else {
$BBSalesValues | Add-Member -Name $($record.SaleValue) -MemberType NoteProperty -Value 1
}
}
}
}
The two resultant objects look like this:
PS> $AASalesValues
10 5 3 50
-- - - --
17 14 3 1
PS> $BBSalesvalues
3 10 5 4
- -- - -
36 12 11 1
I now have the data that I need, however I need to format it in a way that replicates the format of the CSV so I can pass it directly to another existing powershell script that is configured to expect the data in the format that the CSV is in, but I do not want to write the data to a file.
I'd prefer to pass this directly to the next part of the script.
Ultimately what I want to do is to produce a new object/some output that looks like the output from Import-Csv command at the top of this post.
I'd like a new object, say $OverallSalesValues, to look like this:
PS>$overallSalesValues
Sale Values AA BB
50 1 0
10 17 12
5 14 11
4 0 1
3 3 36
In the above example the values from $AASalesValues is listed under the AA column, the values from $BBSalesValues is listed under the BB column, with the rows matching the headers of the two original objects.
I did try this with hashtables but I was unable to work out how to both create them from dynamic values and format them to how I needed them to look.
Finally got there.
$TotalList = #()
foreach($n in 3..200){
if($AASalesValues.$n -or $BBSalesValues.$n){
$AACount = $AASalesValues.$n
$BBcount = $BBSalesValues.$n
$values = [PSCustomObject]#{
'Sale Value'= $n
AA = $AACount
BB = $BBcount
}
$TotalList += $values
}
}
$TotalList
produces an output of
Sale Value AA BB
---------- -- --
3 3 36
4 2
5 14 11
10 18 12
50 1
Just need to add a bit to include '0' values instead of $null.
I'm going to assume that $record contains a list of the database results for either $AASalesValues or $BBSalesValues, not both, otherwise you'd need some kind of selector to avoid counting records of one group with the other group.
Group the records by their SaleValue property as LotPings suggested:
$BBSalesValues = $record | Group-Object SaleValue -NoElement
That will give you a list of the SaleValue values with their respective count.
PS> $BBSalesValues
Count Name
----- ----
36 3
12 10
11 5
1 4
You can then update your CSV data with these values like this:
$file = 'C:\path\to\data.csv'
# read CSV into a hashtable mapping the sale value to the complete record
# (so that we can lookup the record by sale value)
$csv = #{}
Import-Csv $file | ForEach-Object {
$csv[$_.'Sale Values'] = $_
}
# Add records for missing sale values
$($AASalesValues; $BBSalesValues) | Select-Object -Expand Name -Unique | ForEach-Object {
if (-not $csv.ContainsKey($_)) {
$csv[$_] = New-Object -Type PSObject -Property #{
'Sale Values' = $_
'AA' = 0
'BB' = 0
}
}
}
# update records with values from $AASalesValues
$AASalesValues | ForEach-Object {
[int]$csv[$_.Name].AA += $_.Count
}
# update records with values from $BBSalesValues
$BBSalesValues | ForEach-Object {
[int]$csv[$_.Name].BB += $_.Count
}
# write updated records back to file
$csv.Values | Export-Csv $file -NoType
Even with your updated question the approach would be pretty much the same, you'd just add another level of grouping for collecting the sales numbers:
$sales = #{}
$record | Group-Object {$_.list_name.Split()[0]} | ForEach-Object {
$sales[$_.Name] = $_.Group | Group-Object SaleValue -NoElement
}
and then adjust the merging to something like this:
$file = 'C:\path\to\data.csv'
# read CSV into a hashtable mapping the sale value to the complete record
# (so that we can lookup the record by sale value)
$csv = #{}
Import-Csv $file | ForEach-Object {
$csv[$_.'Sale Values'] = $_
}
# Add records for missing sale values
$sales.Values | Select-Object -Expand Name -Unique | ForEach-Object {
if (-not $csv.ContainsKey($_)) {
$prop = #{'Sale Values' = $_}
$sales.Keys | ForEach-Object {
$prop[$_] = 0
}
$csv[$_] = New-Object -Type PSObject -Property $prop
}
}
# update records with values from $sales
$sales.GetEnumerator() | ForEach-Object {
$name = $_.Key
$_.Value | ForEach-Object {
[int]$csv[$_.Name].$name += $_.Count
}
}
# write updated records back to file
$csv.Values | Export-Csv $file -NoType

Powershell counting same values from csv

Using PowerShell, I can import the CSV file and count how many objects are equal to "a". For example,
#(Import-csv location | where-Object{$_.id -eq "a"}).Count
Is there a way to go through every column and row looking for the same String "a" and adding onto count? Or do I have to do the same command over and over for every column, just with a different keyword?
So I made a dummy file that contains 5 columns of people names. Now to show you how the process will work I will show you how often the text "Ann" appears in any field.
$file = "C:\temp\MOCK_DATA (3).csv"
gc $file | %{$_ -split ","} | Group-Object | Where-Object{$_.Name -like "Ann*"}
Don't focus on the code but the output below.
Count Name Group
----- ---- -----
5 Ann {Ann, Ann, Ann, Ann...}
9 Anne {Anne, Anne, Anne, Anne...}
12 Annie {Annie, Annie, Annie, Annie...}
19 Anna {Anna, Anna, Anna, Anna...}
"Ann" appears 5 times on it's own. However it is a part of other names as well. Lets use a simple regex to find all the values that are only "Ann".
(select-string -Path 'C:\temp\MOCK_DATA (3).csv' -Pattern "\bAnn\b" -AllMatches | Select-Object -ExpandProperty Matches).Count
That will return 5 since \b is for a word boundary. In essence it is only looking at what is between commas or beginning or end of each line. This omits results like "Anna" and "Annie" that you might have. Select-Object -ExpandProperty Matches is important to have if you have more than one match on a single line.
Small Caveat
It should not matter but in trying to keep the code simple it is possible that your header could match with the value you are looking for. Not likely which is why I don't account for it. If that is a possibility then we could use Get-Content instead with a Select -Skip 1.
Try cycling through properties like this:
(Import-Csv location | %{$record = $_; $record | Get-Member -MemberType Properties |
?{$record.$($_.Name) -eq 'a';}}).Count

Powershell search CSV against Hashtable

This seems very basic yet I can't find or figure out anywhere.
I have 2 CVS files, I would like to import one and change an ID column to a relevant string contained in another .csv, then export the results to a file. I've been experimenting with hashtables but cannot seem to figure it out.
Current:
Main.csv Reference.csv
Type_ID Category_ID Location_ID Type_ID Type
1 11 111 1 Tablet
2 22 222 2 Laptop
3 33 333 3 Workstation
Would like it to read:
Type Category_ID Location_ID
Tablet 11 111
Laptop 22 222
Workstation 33 333
Here's the code I'm working with:
#import file as hash table
$t = Import-Csv -Path .\Reference.csv -Header "column1","column2"
$HashTable = #{}
foreach($r in $t)
{
Write-Host $r.column1 $r.column2
$HashTable[$r.column1] = $r.column2
}
$HashTable
#compare ID and assign string
ForEach($_.Type_ID in (Import-CSV .\Main.csv)){
If($HashTable.ContainsKey($_.column1)){
$_.column1=$HashTable[$_.column2]
}
$_
} | Export-Csv .\it_works.csv
Any advise is greatly appreciated
First you can create a Hashtable :
Import-Csv C:\dumy\b.csv | % {$t=#{}}{$t+=#{$_.type_id=$_.type}}
Then :
Import-Csv C:\dumy\a.csv | select #{name = "Type"; expression={$t[$_.type_id]}},Category_ID,Location_ID | export-csv 'c:\dumy\c.csv'