Powershell: Iterate through arrays for multiple variables - powershell

I need some mega guidance on my script below. I need to be able to iterate through a csv file that stores tenantNames, app_id, client_secret for my script and wrap a big ForEach loop around it in order for my script to get said data for each tenant inside the CSV:
I'm struggling to visualize the order of the For loops to be able to pass $Tenant, $customer_client_id and $customer_client_secret.
Arrays might be excessive, but it's the most stable way I know to avoid formatting issues etc...
Any assistance or ideas would be super helpful
$master_file = 'C:\temp\apps.csv'
$array_tenant = #()
$array_customer_client_id = #()
$array_customer_client_secret = #()
Import-Csv $master_file | ForEach-Object {
$array_tenant += $_.tenant
$array_customer_client_id += $_.app_id
$array_customer_client_secret += $_.cs
}
$Tenant = ''
$customer_client_id = ''
$customer_client_secret = ''
$Body = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $customer_client_id
Client_Secret = $customer_client_secret
}
$ConnectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$Tenant/oauth2/v2.0/token" -Method POST -Body $Body
$Token = $ConnectGraph.access_token
$file = "C:\temp\$Tenant._users_with_licenses.csv"
$final_results = "C:\temp\$Tenant._results.csv"
$users_array = #()
$user_list = 'https://graph.microsoft.com/beta/users'
$users = Invoke-RestMethod -Headers #{Authorization = "Bearer $($Token)"} -ContentType 'application/json' -Uri $user_list -Method 'GET'
$users.value | Where-Object {$_.assignedLicenses -ne "null"} | Select-Object userPrincipalName | Export-Csv $file -NoTypeInformation
Import-Csv $file | ForEach-Object {
$users_array += $_.userPrincipalName
}
foreach ($item in $users_array) {
$auth_methods = "https://graph.microsoft.com/v1.0/users/$item/authentication/methods"
$get_auth_methods = Invoke-RestMethod -Headers #{Authorization = "Bearer $($Token)"} -ContentType 'application/json' -Uri $auth_methods -Method 'GET'
if (!$get_auth_methods.value) {$get_auth_methods | Export-Csv $final_results -Append -NoTypeInformation}
}

I am going on a whim here and guessing this is what you're after:
$masterFile = 'C:\temp\apps.csv'
Import-Csv -Path $masterFile |
ForEach-Object -Process {
$tenant = $_.tenant
$request = #{
Uri = "https://login.microsoftonline.com/$Tenant/oauth2/v2.0/token"
Method = "POST"
Body = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $_.app_id
Client_Secret = $_.cs
}
}
$connectGraph = Invoke-RestMethod #request
$token = $connectGraph.access_token
$filePath = "C:\temp\$Tenant._users_with_licenses.csv"
$finalResults = "C:\temp\$Tenant._results.csv"
$userRequest = #{
Uri = 'https://graph.microsoft.com/beta/users'
Method = "GET"
Headers = #{
Authorization = "Bearer $token"
ContentType = "application/json"
}
}
$usersGet = Invoke-RestMethod #userRequest
$users = $users.value | Where-Object -Property "assignedLicenses" -NE "null" | Select-Object -ExpandProperty "userPrincipalName"
$users | Export-Csv -Path $filePath -NoTypeInformation -Force
foreach ($user in $users)
{
$finalRequest = #{
Uri = "https://graph.microsoft.com/v1.0/users/$user/authentication/methods"
ContentType = "application/json"
Method = "GET"
Headers = #{
Authorization = "Bearer $Token"
}
}
$getAuthMethod = Invoke-RestMethod #finalRequest
if (-not$getAuthMethod) {
$getAuthMethod | Export-Csv -Path $finalResults -Append -NoTypeInformation
}
}
}
Without really seeing what you expect, quite hard to understand what you currently have. Hopefully this gets you in the right direction! I also made use of splatting as this is a good scenario on when to use it.

Related

powershell cannot bind error on if else block

I am working on a requirement where I have to check if the api call needs to be looped over or not. I am using the below code to accomplish this requirement. If I take out the if else block and write for either loop no loop things work as expected.
PoSh:
$Loop = "1" # 0 for no looping 1 for looping
if ($Loop -eq 1) {
$Header = #{
"authorization" = "Bearer $token"
}
#make REST API call
$Parameters = #{
Method = "GET"
Headers = $Header
ContentType = "application/json"
Body = $BodyJson
}
$startYear = 2014
$endYear = 2022
$Data = {for($year=$startYear; $i -le $endYear; $year=$year+1) {Invoke-RestMethod -Uri "https://api.mysite.com/v1/data/year/Year/" + [string]$year #Parameters -DisableKeepAlive -ErrorAction Stop}} | ConvertTo-Json
}
else {Write-Output "No loop"
$Header = #{
"authorization" = "Bearer $token"
}
#make REST API call
$Parameters = #{
Method = "GET"
Headers = $Header
ContentType = "application/json"
Body = $BodyJson
}
$Data = Invoke-RestMethod -Uri "https://api.mysite.com/v1/data" #Parameters -DisableKeepAlive -ErrorAction Stop | ConvertTo-Json
}
Error:
Cannot bind parameter because parameter 'Uri' is specified more than once. To provide multiple values to parameters that can accept multiple values, use the array syntax.
I have of course no idea what your https://api.mysite.com/v1/data would return and if it is actually needed to convert the returned data to Json at all, but continuing from my comments, try
# make this a Boolean value for clarity
$Loop = $true # $false for no looping $true for looping
# splatting Hashtable for REST API call
$Parameters = #{
Method = "GET"
Headers = #{ "Authorization" = "Bearer $token" }
ContentType = "application/json"
Body = $BodyJson
# you can incorporate these parameters as well
DisableKeepAlive = $true
ErrorAction = 'Stop'
}
if ($Loop) {
Write-Host "Start looping.."
$startYear = 2014
$endYear = 2022
# use the $() subexpression to combine the various outputs and convert that to Json
$Data = $(for ($year = $startYear; $year -le $endYear; $year++) {
Invoke-RestMethod -Uri "https://api.mysite.com/v1/data/year/Year/$year" #Parameters
}) | ConvertTo-Json
}
else {
Write-Host "No loop"
$Data = Invoke-RestMethod -Uri "https://api.mysite.com/v1/data" #Parameters | ConvertTo-Json
}
P.S. The error you saw in your code was caused by the wrong variable you used in the for loop with $i -le $endYear instead of $year -le $endYear. That and the fact that you put the whole loop inside a scriptblock made variables $startYear and $endYear invisible..

Make multiple api calls and hold data from each call in a variable as csv format

So the following code produces a csv file with only the last row. I need all 5 rows (with the test file I'm using) to be populated and exported to a csv file.
$url = "https://example.com/api"
$data_type = "application/json"
$headers = #{
"Content-Type" = $data_type
"Accept" = "application/json"
}
$userId = ""
Import-CSV './userIds.csv' | ForEach-Object {
$userId = $_.id
$body = #{
Identity = $userId
Site = "site_name"
APIKey = "secret"
}
$body_json = $body | Convertto-JSON
$result = Invoke-RestMethod -Method 'Post' -Uri $url -Headers $headers -Body $body_json
}
$result | Export-Csv 'C:\scripts\powershell\output.csv' -NoType
I've tried $list += $result, but I just get a bunch of errors. Any ideas?
Looks like I figured out the magic...
$url = "https://example.com/api"
$data_type = "application/json"
$headers = #{
"Content-Type" = $data_type
"Accept" = "application/json"
}
$userId = ""
$list = #()
Import-CSV './userIds.csv' | ForEach-Object {
$userId = $_.id
$body = #{
Identity = $userId
Site = "site_name"
APIKey = "secret"
}
$body_json = $body | Convertto-JSON
$result = Invoke-RestMethod -Method 'Post' -Uri $url -Headers $headers -Body $body_json
$list += result
}
$list | Export-Csv 'C:\scripts\powershell\output.csv' -NoType
It doesn't make sense to me, because it's an array of tables... but if it works somehow merging the tables into one table... then I'm happy.

Get-content not producing an array that Invoke-restmethod can process

As a follow-up to this question, instead of using a long array in the script I wanted to draw from a text file. So I replaced this:
$URLs = 'http://websiteone.com','http://websitetwo.com','http://websitethree.com'
with this
$URLs = Get-Content ./urlfile.txt
or (functionally the same as far I know) this
$URLs = #(Get-Content ./urlfile.txt)
But I end up with Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
Incorporating the great response form my last question, my foreach loop looks like this:
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL","EXECUTABLE","THREAT_ENTRY_TYPE_UNSPECIFIED"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$Result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
if ( ([string]::IsNullOrEmpty($Result)) ) {} else {write-host $URL "ALERT: Safe browsing match!"}
}
... but this doesn't work if I create the array with the Get-Content cmdlet. If I run the script either way, then type $URLs, I get the exact same data returned. What am I doing wrong with get-content?
The Invoke-RestMethod cmdlet is there to make one Rest request at a time and can't take an array.
You will need to add a forEach loop to step through your $urls one at a time, something like this:
foreach($url in $urls){
$result = Invoke-RestMethod -Uri $url
#do something with $result
}
So to integrate into your sample from the previous question, you should have a urls.txt file which looks like this:
http://google.com
http://small.com
https://fast.com/
And then your code would look like this:
$URLs = get-content .\urls.txt
$HEADERS = #{ 'Content-Type' = "application/json" }
$GOOGLE_API_KEY='[API Key]'
$Uri = 'https://safebrowsing.googleapis.com/v4/threatMatches:find?key='+ $GOOGLE_API_KEY
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
[pscustomObject]#{SiteName=$url;ThreatInfo=$result.Matches}
}
This would load up the list of $urls from your text file, then run a Rest Request on each, storing the result in $result. Finally, it will make a new PowerShell Object with the site name and show you if there are any matches from the Google SafeBrowsing API.
You'll need to run the command interactively and see which properties from $result are meaningful to you, but you can see all of the expected properties in the Google API Docs.
Edit
Found the bug. It turns out when we use Get-Content the object returned back retains some of the document formatting information from the original file! We can see this by inspecting $JSONBODY. We also see that the conversion to Json from [PSCustomObject is leaving a lot of cruft behind too.
To fix this, we should cast $URL into a string using the ToString() method and also ditch casting to [psCustomObject] too as shown below.
$BODY = #{
"client" = #{
"clientId" = "company"; "clientVersion" = "1.0"
};
"threatInfo" = #{
"threatTypes" = "MALWARE",
"SOCIAL_ENGINEERING",
"THREAT_TYPE_UNSPECIFIED",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{
"url" = $URL.ToString()
}
}
}
$JSONBODY = $BODY | ConvertTo-Json

How to Pass JSON Parameter with POST method on powershell 2.0?

I have a Powershell code that is work very fine in powershell version 3.
I need to run this code in powershell 2.0 too. But Invoke-WebRequest not supported in PS version 2.0.
Please help me!
$params = "metrics[]=failed:count"
$failed = (Invoke-WebRequest -Uri http://localhost:9000/stats -Method POST -Body $params -ContentType "application/json").Content
$x = $failed | ConvertFrom-Json
Untested, but I think this may help:
$params = "metrics[]=failed:count"
$result = #{}
try{
$request = [System.Net.WebRequest]::Create('http://localhost:9000/stats')
$request.Method = 'POST'
$request.ContentType = 'application/json'
$request.Accept = "application/json"
$body = [byte[]][char[]]$params
$upload = $request.GetRequestStream()
$upload.Write($body, 0, $body.Length)
$upload.Flush()
$upload.Close()
$response = $request.GetResponse()
$stream = $response.GetResponseStream()
$streamReader = [System.IO.StreamReader]($stream)
$result['StatusCode'] = $response.StatusCode
$result['StatusDescription'] = $response.StatusDescription
$result['Content'] = $streamReader.ReadToEnd()
$streamReader.Close()
$response.Close()
}
catch{
throw
}
# I suggest checking $result.StatusCode here first..
$x = $result.Content | ConvertFrom-Json

Coinspot API with PowerShell

I'm struggling to access the Coinspot API from PowerShell. No matter what I do I always get the "no nonce" error back from the API:
$VerbosePreference = 'Continue'
$key = ''
$secret = ''
$epoc_start_date = ("01/01/1970" -as [DateTime])
[int]$nonce = ((New-TimeSpan -Start $epoc_start_date -End ([DateTime]::UtcNow)).TotalSeconds -as [string])
$baseUrl = 'www.coinspot.com.au/api'
$resourcePath = '/my/orders'
$url = 'https://{0}{1}&nonce={2}' -f $baseUrl, $resourcePath, $nonce
$encoded = New-Object System.Text.UTF8Encoding
$url_bytes = $encoded.GetBytes($url)
# create hash
$hmac = New-Object System.Security.Cryptography.HMACSHA512
$hmac.key = [Text.Encoding]::ASCII.GetBytes($secret)
$sha_result = $hmac.ComputeHash($url_bytes)
#remove dashes
$hmac_signed = [System.BitConverter]::ToString($sha_result) -replace "-";
$headers = #{
sign = $hmac_signed
key = $key
'content-type' = 'application/json'
}
$result = Invoke-RestMethod -Uri $url -Method Post -Headers $headers
$result
Alternatively I have already tested this:
$VerbosePreference = 'Continue'
$key = ''
$secret = ''
$epoc_start_date = ("01/01/1970" -as [DateTime])
[int]$nonce = ((New-TimeSpan -Start $epoc_start_date -End ([DateTime]::UtcNow)).TotalSeconds -as [string])
$baseUrl = 'www.coinspot.com.au/api'
$resourcePath = '/my/orders'
$url = 'https://{0}{1}' -f $baseUrl, $resourcePath
$body = #{
nonce = $nonce
}
$encoded = New-Object System.Text.UTF8Encoding
$body_bytes = $encoded.GetBytes($body)
# create hash
$hmac = New-Object System.Security.Cryptography.HMACSHA512
$hmac.key = [Text.Encoding]::ASCII.GetBytes($secret)
$sha_result = $hmac.ComputeHash($body_bytes)
#remove dashes
$hmac_signed = [System.BitConverter]::ToString($sha_result) -replace "-";
Invoke-RestMethod -Uri $url -Method Post -Headers #{sign = $hmac_signed ; key = $key ; 'content-type' = 'application/json' } -Body $($body | ConvertTo-Json)
The second gives me an invalid status error.
I have a feeling there's something wrong with my header.
Coinspot support responded:
Apologies for this.
Our current API system is way out of date and needs to be updated.
We know that we need to support the developers as best as we can but our current dev team are very busy with other things at the
moment.
They are aware of this and plan to update it as soon as possible, but right now there is no ETA for this.
Very sorry the inconvenience.