Calling #odata.nextlink from powershell - powershell

I'm trying to figure out how to call the odata.nextlink from a powershell script im writing to get Azure AD signin information for users.
$LastLogin = Invoke-WebRequest -Headers $AuthHeader1 -Uri "https://graph.microsoft.com/beta/users?`$select=displayName,userPrincipalName,signInActivity" -Verbose
$result = ($LastLogin.Content | ConvertFrom-Json).Value
$result | select DisplayName,UserPrincipalName,#{n="LastLoginDate";e={$_.signInActivity.lastSignInDateTime}}
and it results in the first 100 results being disaplayed
if I view the $lastLogin output I can see the content includes the odata.nextlink option but I can't seem to get the uri to pass into a while loop to get all the results
$lastLogin output image
if I do $lastLogin."#odata.nextLink' it just returns a null value.
Where am I going wrong?
Thanks

In the second step rather using the
$result = ($LastLogin.Content | ConvertFrom-Json).Value
Use $result = ($LastLogin.Content | ConvertFrom-Json) and then pull the nextLink using the $result.'#odata.nextLink'
It worked for me.

Related

Running ForEach-Object -Parallel, data missing from export

I have some working code that basically queries 2 different Graph API endpoints, then searches for a match in the User Principal Name column, and inserts the extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber column and values to the exported csv (Thanks to the user #PMental for this solution) This column derives from attribute that was recently extended from our on premises AD.
This code works perfectly fine, however if I try to parallelize it, I get no results in the extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber column.
Is this because once it is being parallelized, I'm not able to share variables between the parallel processes? If so, how on earth do I accomplish this?
Code below - if you remove the -Parallel, it works fine:
$graphApiUri = "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D90')"
$Uri = "https://graph.microsoft.com/v1.0/users?`$select=userPrincipalName,extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber"
$O365Report = Invoke-RestMethod -Method Get -Uri $graphApiUri -Headers $headerParams | ConvertFrom-Csv
# If the result is more than 999, we need to read the #odata.nextLink to show more than one side of users
$UserDetails = while (-not [string]::IsNullOrEmpty($uri)) {
# API Call
$apiCall = try {
Invoke-RestMethod -Headers $headerParams -Uri $uri -Method Get
}
catch {
$errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
}
$uri = $null
if ($apiCall) {
# Check if any data is left
$uri = $apiCall.'#odata.nextLink'
$apiCall
}
}
Write-Output "Matching UPN to employeeNumber..."
$O365Report | ForEach-Object -Parallel {
$CurrentEmpNumber = $UserDetails.value |
Where-Object userPrincipalName -eq $_.'User Principal Name' |
Select-Object -ExpandProperty extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber -ErrorAction SilentlyContinue
$_ | Add-Member -MemberType NoteProperty -Name extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber -Value $CurrentEmpNumber
}
$O365Report | Export-Csv $ReportCSV -NoTypeInformation
Write-Output "Report saved to $ReportCSV."
When inside of a ForEach-Object -Parallel script block, and you are trying to reference variables which were created outside of it, you need to preface the variable name with using: so it would be $using:UserDetails
Examples:
Returns nothing because $test isn't accessible within the scope of the parallel script block:
$test = 1;
0..5 | % -Parallel { $test; };
Returns the value of $test five times because by using $using:test you are now able to see its value:
$test = 1;
0..5 | % -Parallel { $using:test; };
From documenation:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7.1
The ForEach-Object -Parallel parameter set runs script blocks in parallel on separate process threads. The $using: keyword allows passing variable references from the cmdlet invocation thread to each running script block thread. Since the script blocks run in different threads, the object variables passed by reference must be used safely. Generally it is safe to read from referenced objects that don't change. But if the object state is being modified then you must used thread safe objects, such as .Net System.Collection.Concurrent types (See Example 11).
Personal note:
I would also recommend using -ThrottleLimit to limit its max degrees of paralellism. The default is 5, but you may want more or less than that depending on testing.

Getting string from what appears to be a hash-map in PowerShell

In PowerShell, I do this command:
$w = iwr -usedefaultcredentials -uri http://acme.corp/anvils.aspx -method 'POST' -Body '{"ctl00_MainEntityNumber:"Wiley"}'
That returns an object, part of which is an object called InputFiles, one of which is an object called __VIEWSTATE. I want to get the string value of __VIEWSTATE. So I do this:
$f = $w.inputfields | where id -eq __VIEWSTATE | select-object value | select-string value
which gives me a MatchInfo object. If I just print out the object, I get
#{value=/wEPDwUJODU5MDM5MTc0DxYEHhBQYWdlRW50aXR5TnVtYmVyZh4Yb0luc3VyYW5jZVNlYXJjaENyaXRlcmlhMv0BAAEAAAD/////AQAAAAAAAAAMAgAAAFFJRE9JLlJlZ3VsYXRlZEV ....}
which looks to me like a Hashmap or something. But when I try $f.value or $f['value'] I get nothing, or if I $f[0], I get the whole thing (including the #{value=.. part) So the question is, how do I get just the string after the #{value=\ ? Do I have to parse it out manually?
If you're just wanting the value itself then change it to
$f = $w.inputfields | where id -eq __VIEWSTATE | Select-Object -ExpandProperty value

Issues pulling back values while web scraping a table

I am attempting to pull the text from a table on a webpage. I pull the webpage using Invoke-WebRequest, set that variable to show "AllElements" and attempt to only pull the inner values matching "Table"; but when I run the script nothing is pulled back and no errors are shown.
$URI = 'https://www.python.org/downloads/release/python-2716/'
$R = Invoke-WebRequest -URI $URI
$R.AllElements|?{$_.Class -eq "table"}|select innerText
I was hoping to show the values of the table on the python.org site, but when the script is run nothing is returned.
How do I solve this problem?
That is because there are no tables or table class, it's a div with dynamically generated ordered list items.
You can see this in the browser developer tools, using F12 in Edge or similar in Firefox, Chrome, etc...
$URI = 'https://www.python.org/downloads/release/python-2716'
$R = Invoke-WebRequest -URI $URI
$R.AllElements |
Where {$_.Class -eq 'container' }
$R.AllElements |
Where {$_.Class -eq 'list-row-container menu' }
($R.AllElements |
Where {$_.class -eq 'list-row-container menu'}).innerText
($R.AllElements |
Where {$_.Class -eq 'release-number' })
($R.AllElements |
Where {$_.Class -eq 'release-number' }).outerHTML
(($R.AllElements |
Where {$_.Class -eq 'release-number' }).outerHTML -split '<a href="|/">Python')[2]
Or just do this...
$R.Links
$R.Links.href
$R.Links.href -match 'downloads'

How to Display the Last Half of a String in the Results Returned by windows "findstr" command?

I have a windows 2012 instance on AWS in which I am trying to return the instance ID from the CLI. I can successfully return that info into a variable with this command:
$instanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id
I can then echo the contents of that variable and filter out the pertinent line:
PS C:\Users\Administrator> echo $instanceId | findstr /b /c:"Content "
Content : i-4bee88888bd72g2a
The problem I have is that I wish to return only the string after the colon, so the output would look like:
i-4bee88888bd72g2a
What switch can I add to findstr to filter out that string? What is the Microsoft equivalent to sed?
PowerShell outputs objects, not text. When you run this command:
Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id
it outputs a string representation of an object with a Content property. To select only the value of that property, you can use Select-Object -ExpandProperty as follows:
Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id | Select-Object -ExpandProperty Content
This tells PowerShell: "There's an output object, and I want only the value of its Content property."
You can assign this to your variable:
$instanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id | Select-Object -ExpandProperty Content
You can also probably write it this way:
$instanceId = (Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id).Content
(That is, the ( ) enclose an expression, and you are getting the Content property of the expression's output object.)
I found a better solution that returns the exact results; it uses 'replace', the MS version of 'sed':
$instanceId = Invoke-WebRequest -Uri http://169.254.169.254/latest/meta-data/instance-id
$contentString = $instanceId | findstr /b /c:"Content "
$desiredresult = $contentString -replace "(Content :)\s([a-z]+)",'$2'
$desiredresult
Which returns the exact results:
i-4bee88888bd72g2a
I may have been barking up the wrong tree with the command findstr. I found that I can display the exact string withe command select-string:
$instanceId | select-string -Pattern "i-"
Which returns the results:
i-4bee88888bd72g2a
(But it included blank lines before and after the results, which I may have to discard, TBD.)

PowerShell export multiple objects as csv

I have two columns of data the first is a string array the second is actually an object. I am looking for a simple way of exporting this as a csv. I have a version with a foreach loop that builds each string up but it seams like over kill. I have been trying to use select and select object to get it out somehow. Note I am just a beginner at powershell so I may be missing something.
My first attempt:
$data | Select-Object -ExpandProperty reports | Select -ExpandProperty data | Select -ExpandProperty rows | Format-Table $_.dimensions
Results in:
dimensions metrics
---------- -------
{New Visitor, "Mozilla} {#{values=System.Object[]}}
My second one went as far as looping
foreach ($report in $data.reports) {
"Rows:" + $report.data.rows.Count
foreach ($row in $report.data.rows) {
$output = ""
foreach($dim in $row.dimensions) {
$output += $dim + $seporator
}
foreach($met in $row.metrics) {
foreach($v in $met.values) {
$output += $v + $seporator
}
}
#| Out-File -Append C:\Users\linda_l\Documents\debug.txt
$output
}
}
There is a potential for a lot of data here so I would really like to avoid the string building solution.
Note: Data comes from the Google Analytics reporting api
$data = Invoke-RestMethod -ContentType 'application/json' -Uri "https://analyticsreporting.googleapis.com/v4/reports:batchGet?access_token=$($token.access_token)" -Method POST -Body $analyticsRequest
reports : {#{columnHeader=; data=}}
From comment:
$data | Export-Csv -Path "C:\Users\linda_l\Documents\debug2.csv"
System.Management.Automation.PSCustomObject reports System.Object[]
Optimal output csv
New Visitor,S40 Ovi Browser,1,2
New Visitor,Safari,3,4
Note its up on Github steps for getting a refreshtoken
Data is coming from the Google analytics reporting API