Parsing Rest query output - regex

I am trying to parse the output of a rest api query of the form
$response = Invoke-RestMethod -Uri $uri -Headers $headers
$response.name | Select-String -Pattern ^role
returns an output similar to this below (elements separated by ::)
role::servicing2
role::collaboration::lei
role::commercial_lines::npds
role::nvp::windows::ucce_gold
role::oracle::linux::oracle_oid
role::splunk::splunk_enterprise::add_on
I need to read this output line by line and parse.
If there are just 2 elements eg. role::servicing2 ignore the line
If there are 3 elements, ignore the first element "role", prepend puppet_ to the second element and it becomes the project, the third element is the role (OS is unknown)
If there are 4 or more elements, ignore the first element "role", prepend puppet_ to the second element and it becomes the project, if the third element is "windows" or "linux" that is the OS, else OS is "unknown", and the last element \:\:'(\w+)'$ is the role.
Need an output in the form of an array or table or list in this format
(Don't necessarily need header)
Project OS Role
puppet_collaboration unknown lei
puppet_commercial_lines unknown npds
puppet_nvp windows ucce_gold
puppet_oracle linux oracle_oid
puppet_splunk unknown add_on
I have tried various regex expressions. Couldn't figure out the logic of walking this line by line and parsing appropriately into a list or array.

I think below code should do what you want:
$roles = #'
role::servicing2
role::collaboration::lei
role::commercial_lines::npds
role::nvp::windows::ucce_gold
role::oracle::linux::oracle_oid
role::splunk::splunk_enterprise::add_on
'# -split '\r?\n'
$result = $roles | ForEach-Object {
$parts = $_ -split '::'
switch ($parts.Count) {
2 { continue } # ignore this line
3 {
[PsCustomObject]#{
'Project' = 'puppet_{0}' -f $parts[1]
'OS' = 'unknown'
'Role' = $parts[2]
}
}
default {
[PsCustomObject]#{
'Project' = 'puppet_{0}' -f $parts[1]
'OS' = if ('windows', 'linux' -contains $parts[2]) {$parts[2]} else {'unknown'}
'Role' = $parts[-1]
}
}
}
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'D:\roles.csv' -NoTypeInformation
For testing I have put the result of your $response.name | Select-String -Pattern ^role in a here-string.
Output:
Project OS Role
------- -- ----
puppet_collaboration unknown lei
puppet_commercial_lines unknown npds
puppet_nvp windows ucce_gold
puppet_oracle linux oracle_oid
puppet_splunk unknown add_on

Related

PowerShell filer out invalid AD users using -filte {SamAccountName -eq $_} but with regex

I am trying to filter AD for user names based on computer names which contain the user name, like XXXXXX01BLOGGSJ (BLOGGSJenter code here is the user name in this example)
In order to extract the user name, I use this method:
"XXXXXX01BLOGGSJ" | %{($_ -split '\d+')[-1]}
The output is BLOGGSJ
However, I need to filter many computer names like this, a small percentage of which have invalid usernames in the machine name like "XXXXXX01RUBBISH"
In order to stop the inevitable errors from appearing I am trying to use the -filter {SamAccountName $_} method which works like this:
"BLOGGSJ", "RUBBISH" | % {Get-ADUser -Server domain.com -Filter{SamAccountName -eq $_ }} | select Name
But not when I attempt to do this, which is what I want to do:
“XXXXXX01BLOGGSJ”, “XXXXXX01BLOGGSJ” | % {Get-ADUser -Server domain.com -Filter{SamAccountName -eq "'($_ -split '\d+')[-1]'"}} | select Name
……or various permutations of that. So I am struggling with the syntax I think.
I know I can do this instead:
"XXXXXX01BLOGGSJ","XXXXXX01RUBBISH" | %{($_ -split '\d+')[-1]} | %{Get-ADUser -Server domain.com -Filter {SamAccountName -eq $_ }} | Select Name
but there is something else happening further down the pipe that requires me to do it in the way shown above.
Any help please.
Especially because you say something else is happening further down, I would suggest not trying to do all in a one-line code.
This should get you on your way:
"XXXXXX01BLOGGSJ","XXXXXX01RUBBISH" | ForEach-Object {
$name = ($_ -split '\d+')[-1]
$user = Get-ADUser -Server domain.com -Filter "SamAccountName -eq '$name'" -ErrorAction SilentlyContinue
if ($user) {
# a user with that SamAccountName was found
[PsCustomObject]#{
ComputerName = $_
SamAccountName = $user.SamAccountName
UserName = $user.Name
}
}
else {
# user not found
[PsCustomObject]#{
ComputerName = $_
SamAccountName = $name
UserName = "User Not found in AD"
}
}
}
Output:
ComputerName SamAccountName UserName
------------ -------------- --------
XXXXXX01BLOGGSJ bloggsj Joe Bloggs
XXXXXX01RUBBISH RUBBISH User Not found in AD

Parse email body paragragh in Powershell

I am creating a script to parse outlook email body, so that I can get say an (ID number, date, name) after strings ID: xxxxxx Date: xxxxxx Name:xxxxx. I was looking around and could not fine anything that allows me to take the string after a match.
What I manage so far is to query for the email that was send by the specific users from outlook.
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
foreach ($items in $inbox.items){if (($items.to -like "*email*") -or ($items.cc -like "*email.add*")){$FindID = $items.body}}
Now that I have the email body in the for loop I am wondering how I can parse the content?
In between the paragraphs will be a text something like this
ID: xxxxxxxx
Name: xxxxxxxxx
Date Of Birth : xxxxxxxx
I did some testing on the below to see if I can add that into the for loop but it seem like I cannot break the paragraphs.
$FindID| ForEach-Object {if (($_ -match 'ID:') -and ($_ -match ' ')){$testID = ($_ -split 'ID: ')[1]}}
I get the following results which I cannot get just the ID.
Sample Result when i do $testID
xxxxxxxx
Name: xxxxxxxxx
Date Of Birth : xxxxxxxx
Regards,
xxxxx xxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
How do I get just the results I want? I am just struggling at that portion.
You'll need a Regular Expression with (named) capture groups to grep the values. See example on rexgex101.com.
Provdid $item.bodyis not html and a single string, this could work:
## Q:\Test\2018\07\24\SO_51492907.ps1
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder(
[Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
## see $RE on https://regex101.com/r/1B2rD1/1
$RE = [RegEx]'(?sm)ID:\s+(?<ID>.*?)$.*?Name:\s+(?<Name>.*?)$.*?Date Of Birth\s*:\s*(?<DOB>.*?)$.*'
$Data = ForEach ($item in $inbox.items){
if (($item.to -like "*email*") -or
($item.cc -like "*email.add*")){
if (($item.body -match $RE )){
[PSCustomObject]#{
ID = $Matches.ID
Name = $Matches.Name
DOB = $Matches.DOB
}
}
}
}
$Data
$Data | Export-CSv '.\data.csv' -NoTypeInformation
Sample output with above anonimized mail
> Q:\Test\2018\07\24\SO_51492907.ps1
ID Name DOB
-- ---- ---
xxxxxx... xxxxxxx... xxxxxx...
I don't have Outlook available at the moment, but i think this will work
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$inbox.items | Where-Object { $_.To -like "*email*" -or $_.CC -like "*email.add*"} {
$body = $_.body
if ($body -match '(?s)ID\s*:\s*(?<id>.+)Name\s*:\s*(?<name>.+)Date Of Birth\s*:\s*(?<dob>\w+)') {
New-Object -TypeName PSObject -Property #{
'Subject' = $_.Subject
'Date Received' = ([datetime]$_.ReceivedTime).ToString()
'ID' = $matches['id']
'Name' = $matches['name']
'Date of Birth' = $matches['dob']
}
}
}

Powershell script to open another script as admin

I have a script that I can double click and it'll open other scripts as admin. Works with some things but not everything. For one script, it opens the next window and then immediately closes it. For another, I get this error:
At MYPATH\InstallClient.ps1:33 char:78
+ ... tall_x64.msi" -force -recurse -ErrorAction Stop #Cleans out the file ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The string is missing the terminator: ".
At MYPATH\InstallClient.ps1:27 char:31
+ ForEach ($entry in $computers){ #start of foreach loop
+ ~
Missing closing '}' in statement block or type definition.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
Below is the script to open a script as an admin:
Function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "PS1 (*.ps1)| *.ps1"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$inputfile = Get-FileName "MYPATH\Scripts"
powershell.exe -noprofile -command "&{start-process powershell -ArgumentList '-NoExit -noprofile -file $inputfile' -verb RunAs}"
This is the script that it gives the previous error for while trying to open:
Function Get-FileName($initialDirectory) #Function to choose a file
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "MSI (*.msi)| *.msi" #type of files that will be available for selection
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$inputfile = Get-FileName "MyPath" #Directory that is going to open to select a file from
Function Get-FileName($initialDirectory) #Function to choose a file
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "CSV (*.csv)| *.csv" #type of files that will be available for selection
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$inputfile1 = Get-FileName "MyPath\ServerLists"
$computers = import-csv $inputfile1
ForEach ($entry in $computers){ #start of foreach loop
$computername = $entry.computernames #this saves the single entry under computernames for each entry in csv file
Copy-item $inputfile -container -recurse \\$computername\C$\windows\temp #this copies the msi file that we selected to the computer entry called from the csv file's temp folder
Invoke-Command -Computername $computername –ScriptBlock {Start-process -Wait "C:\windows\temp\ShadowSuiteClientInstall_x64.msi"} | out-null #This starts the msi file that we just copied and waits for the installation to be completed before moving on
If($?){ #If the last command was successful
Echo "Installed ShadowSuiteClientInstall_x64 on $computername."
Remove-Item "\\$computername\C$\windows\temp\ShadowSuiteClientInstall_x64.msi" -force -recurse -ErrorAction Stop #Cleans out the file we copied into the temp folder
}
}
Does anyone have any ideas on why this will open some things fine but give this error for this script and immediately close other scripts without running them? Does anyone have a better way to navigate through scripts and select one to open as admin?
Ok I figured this out. I loaded the script into powershell ISE and I saw that it was compiling it incorrectly. It kept turning the -Scriptblock into an ae symbol instead of the - in front of scriptblock. Weird AF IMO but ok, I fixed it in ISE, which I recommend to anyone struggling with weird compiling errors like this.

Select a particular type of word from a text file and load it in a Variable

I am trying to use a power shell script to read the contents of a file and pick a specific type of word from it. I need to load the word that is found as a variable which I intend to use further downstream.
This is how my input file looks like:
{
"AvailabilityZone": "ap-northeast-1b",
"VolumeType": "gp2",
"VolumeId": "vol-087238f9",
"State": "creating",
"Iops": 100,
"SnapshotId": "",
"CreateTime": "2016-09-15T12:17:27.952Z",
"Size": 10
}
The specific word I would like to pick is vol-xxxxxxxx.
I used this link to write my script
How to pass a variable in the select-string of powershell
This is how I am doing it:
$Filename = "c:\reports\volume.jason"
$regex = "^[vol-][a-z0-9]{8}$"
$newvolumeid=select-string -Pattern $regex -Path $filename > C:\Reports\newVolumeid.txt
$newVolumeid
When I run this script it runs but does not give any response. Seems somehow the output of select string is not loaded into the variable $newvolumeid.
Any idea how to resolve this? Or what I am missing?
PS: The post mentioned above is about 3 years old and doesn't work hence I am reposting.
You are trying to read a property of a JSON object. Instead of using regex, you can parse the JSON and select the property using:
Get-Content 'c:\reports\volume.jason' | ConvertFrom-Json | select -ExpandProperty VolumeId
Try this
$Inpath = "E:\tests\test.txt"
$INFile = Get-Content $Inpath
$NeedsTrimming = $INFile.Split(" ") | ForEach-Object {if ($_ -like '*vol-*'){$_}}
$FirstQuote = $NeedsTrimming.IndexOf('"')
$LastQuote = $NeedsTrimming.LastIndexOf('"')
$vol = $NeedsTrimming.Substring(($FirstQuote + 1),($LastQuote - 1))
$vol

netsh result to a PowerShell object

I am trying to work with NETSH from PowerShell. I want see a result from this command such as an object, but netsh returns a string:
netsh wlan show hostednetwork | Get-Member
TypeName: System.String
...
My script must work on system with rather localization, and I can't use -match for parsing a string to an object directly.
How I can solve my trouble?
$netshResult = Invoke-Command -Computername localhost {netsh int tcp show global}
$result = #{}
$netshObject = New-Object psobject -Property #{
ReceiveSideScalingState = $Null
ChimneyOffloadState = $Null
NetDMAState = $Null
}
$netshResult = $netshResult | Select-String : #break into chunks if colon only
$i = 0
while($i -lt $netshResult.Length){
$line = $netshResult[$i]
$line = $line -split(":")
$line[0] = $line[0].trim()
$line[1] = $line[1].trim()
$result.$($line[0]) = $($line[1])
$i++
}
$netshObject.ReceiveSideScalingState = $result.'Receive-Side Scaling State'
$netshObject.ChimneyOffloadState = $result.'Chimney Offload State'
$netshObject.NetDMAState = $result.'NetDMA State'
You got a few alternatives, none of which are nice.
1) Read the netsh output into a string[] and use a custom record parser to create your own object. That is, look at the output on different locales and find out if, say, Hosted newtork settings is always the first header followed by bunch of - characters. If that's the case, assume that next element in array is Mode and so on. This is very error prone, but usually MS command line tools only translate messages, not their order.
2) Look for .Net API for the same information. There is System.Net.NetworkInformation which contains a bunch of connection things. It's a start, though I am not sure if it has info you need.
3) Failing the previous options, use P/Invoke to call native Win32 API. It's a lot of work, so look for pre-existing wrapper libraries before rolling your own.
I recently wrote a cmdlet to parse arbitrary, multi-line text using regular expressions, called ConvertFrom-Text. (Not a great name, if you ask me, but it conforms to the PowerShell naming rules; suggestions are welcome!) So assuming you have that cmdlet, here is one possible solution to your question. (Caveat emptor! The regular expression given was derived from a very small sample of netsh output, so may need some tuning.)
$regex = [regex] '(?ms)(?:^\s*$\s*)?^(?<section>.*?)\s*-+\s*(?<data>.*?)\s*^\s*$'
$result = netsh wlan show hostednetwork | Out-String |
ConvertFrom-Text -pattern $regex -multiline
$result | % {
$dataObj = [PsCustomObject]#{}
$_.Data -split "`r`n" | % {
$element = $_ -split '\s*:\s*'
Add-Member -InputObject $dataObj -MemberType NoteProperty -Name $element[0].Trim() -Value $element[1].Trim()
}
$_.Data = $dataObj # Replace data text with data object
}
$result
On my test system, netsh wlan show hostednetwork returns this:
Hosted network settings
-----------------------
Mode : Allowed
Settings : <Not configured>
Hosted network status
---------------------
Status : Not available
And the output of the $result variable in the code above yields this:
section data
------- ----
Hosted network settings #{Mode=Allowed; Settings=<Not configured>}
Hosted network status #{Status=Not available}
So $result is an array of objects with section and data properties, and the latter is an object with properties defined by the output of the netsh command.
Of course, the above does not get you very far without the ConvertFrom-Text cmdlet. So here is the implementation. (I have copious documentation and examples for it, which will be publicly available once I eventually add it to my open-source PowerShell library.)
filter ConvertFrom-Text
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$InputObject,
[Parameter(Mandatory=$true,Position=1)]
[regex]$Pattern,
[switch]$RequireAll,
[switch]$Multiline
)
if ($Multiline) {
$dataString = $InputObject -join "`n"
IterateByMatch $dataString $Pattern
}
else {
IterateByLine $InputObject $Pattern
}
}
function IterateByLine([string[]]$data, [regex]$regex)
{
$data | ForEach-Object {
if ($PSItem -match $regex)
{
New-Object PSObject -Property (GetRegexNamedGroups $matches)
}
elseif ($RequireAll) {
throw "invalid line: $_"
}
}
}
function IterateByMatch([string[]]$data, [regex]$regex)
{
$regex.matches($data) | Foreach-Object {
$match = $_
$obj = new-object object
$regex.GetGroupNames() |
Where-Object {$_ -notmatch '^\d+$'} |
Foreach-Object {
Add-Member -InputObject $obj NoteProperty `
$_ $match.groups[$regex.GroupNumberFromName($_)].value
}
$obj
}
}
function Get-RegexNamedGroups($hash)
{
$newHash = #{};
$hash.keys | ? { $_ -notmatch '^\d+$' } | % { $newHash[$_] = $hash[$_] }
$newHash
}