Pass entire hashtable to Invoke-Expression - powershell

I would like to ask you for an question about passing hashtable to Invoke-Expression.
Iam writing simple E2E monitoring and mentioned hashtable is used as a body containing creds for HTTP form to log jira.
It works for me fine, but from specific reason I would like to create this Invoke-Webrequest dynamically, depending on recieved arguments.
And there is my catch.
Thus, I don't know, how to pass hashtable (other data types are ok, like a string or int) to Invoke Expression.
It is always presented like System.Collections.Hashtable
$uri = 'https://exdom.com/login.jsp?saml_sso=false'
$method = "POST"
$postParams = #{
os_username = "username";
os_password = "password";
login = "true"
}
$scriptBlock = {
param(
[Parameter(Mandatory=$true,Position=1)][string]$uri,
[Parameter(Mandatory=$false,Position=2)][string]$method,
[Parameter(Mandatory=$true,Position=3)][hashtable]$postParams
)
$commandFragments = #()
$commandFragments += "Invoke-WebRequest"
if ( $PSBoundParameters.ContainsKey('uri')){
$commandFragments += " -Uri $uri"
}
if ( $PSBoundParameters.ContainsKey('method')){
$commandFragments += " -Method $method"
}
if ( $PSBoundParameters.ContainsKey('postParams')){
$commandFragments += " -Body $postParams"
}
$commandFromFragments = $commandFragments -join ''
(Invoke-Expression -Command $commandFromFragments).Content | Out-File 'c:\tmp\response3.html'
(Invoke-Expression -Command "Invoke-WebRequest -Uri https://exdom.com/login.jsp?saml_sso=false -Method POST -Body #(#{'os_username' = 'username#mydomain.com'; 'os_password' = 'mypassword'; 'login' = 'true'})").Content | Out-File 'c:\tmp\response4.html'
(Invoke-WebRequest -Method $method -Uri $uri -Body $postParams).Content | Out-File 'c:\tmp\response5.html'
}
Invoke-Command -ScriptBlock $scriptBlock -ArgumentList ($uri, $method, $postParams)
Iam missing something basic, I guess.
May I ask you for an advice?
Thanks, Marcel

Lets talk about whats wrong.
$commandFragments += " -Body $postParams"
You are turning a HashTable into a string. Which is not possible. So what we can do is convert it into something. Now what should we convert into? Invoke-WebRequest -body
This can be done with Json. So you could use " -Body $($postParams | convertto-json)"
But this is only saving the json to a string which still wouldnt work because the Json needs to be in a string inside the command Invoke-WebRequest. So the fix would be to surround the JSON with single quotes. " -Body '$($postParams | ConvertTo-Json)'"
We also have some small fixes we can do for efficiency. Like the if statements looking
$PSBoundParameters.GetEnumerator() | %{
switch($_.Key){
"uri" { $commandFragments += " -Uri $uri" }
"method" { $commandFragments += " -Method $method" }
"postParams" { $commandFragments += " -Body '$($postParams | ConvertTo-Json)'" }
}
}
The final product being
$uri = 'https://exdom.com/login.jsp?saml_sso=false'
$method = "POST"
$postParams = #{
"os_username" = "username";
"os_password" = "password";
"login" = "true"
}
$scriptBlock = {
param(
[Parameter(Mandatory=$true,Position=1)][string]$uri,
[Parameter(Mandatory=$false,Position=2)][string]$method,
[Parameter(Mandatory=$true,Position=3)][hashtable]$postParams
)
$commandFragments = $("Invoke-WebRequest")
$PSBoundParameters.GetEnumerator() | %{
switch($_.Key){
"uri" { $commandFragments += " -Uri $uri" }
"method" { $commandFragments += " -Method $method" }
"postParams" { $commandFragments += " -Body '$($postParams | ConvertTo-Json)'" }
}
}
(Invoke-Expression -Command $($commandFragments -join '')).Content | Out-File 'c:\tmp\response3.html'
(Invoke-Expression -Command "Invoke-WebRequest -Uri https://exdom.com/login.jsp?saml_sso=false -Method POST -Body #(#{'os_username' = 'username#mydomain.com'; 'os_password' = 'mypassword'; 'login' = 'true'})").Content | Out-File 'c:\tmp\response4.html'
(Invoke-WebRequest -Method $method -Uri $uri -Body $postParams|ConvertTo-Json).Content
}
Invoke-Command -ScriptBlock $scriptBlock -ArgumentList ($uri, $method, $postParams)

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..

Nessus IO Powershell API HTML/PDF report

When exporting PDF & HTML format reports the reports are empty, best I can tell there needs to be a report attribute but after 5 hours of running through the API and searching every which way I can think of I am not finding anything referencing that.
For those interested, this is the starting script before I started optimizing it.
https://github.com/Pwd9000-ML/NessusV7-Report-Export-PowerShell/blob/master/NessusPro_v7_Report_Exporter_Tool.ps1
add-type #"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"#
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$GNR = #{
OutputDir = "$Env:SystemDrive\Nessus\$(([DateTime]::Now).ToString("yyyy-MM-dd"))"
StatusUri = [System.Collections.ArrayList]::new()
}
#------------------Input Variables-----------------------------------------------------------------
$Baseurl = "https://$($env:COMPUTERNAME):8834"
$Username = <Removed>
$Password = <Removed>
$ContentType = "application/json"
$POSTMethod = 'POST'
$GETMethod = 'GET'
#------------------Stage props to obtain session token (Parameters)--------------------------------
$session = #{
Uri = $Baseurl + "/session"
ContentType = $ContentType
Method = $POSTMethod
Body = convertto-json (New-Object PSObject -Property #{username = $Username; password = $Password})
}
#------------------Commit session props for token header X-cookie----------------------------------
$TokenResponse = Invoke-RestMethod #session
if ($TokenResponse) {
$Header = #{"X-Cookie" = "token=" + $TokenResponse.token}
} else {
Write-nLog -Message "Error occured obtaining session token. Script Terminating... Please ensure Username and Password Correct." -Type Error -TerminatingError
}
IF (![System.IO.Directory]::Exists($GNR.OutputDir)) {
New-Item -Path $GNR.OutputDir -ItemType directory -Force |Out-Null
}
#------------------Output completed scans----------------------------------------------------------
$Scans = (Invoke-RestMethod -Uri "$baseurl/scans" -Headers $Header -Method $GETMethod -ContentType "application/json").scans
ForEach ($Format in #("nessus","pdf")) {
$StatusURI = [System.Collections.ArrayList]::new()
$StatusArray = [System.Collections.ArrayList]::new()
ForEach ($Scan in $Scans) {
Add-Content -Path "$($GNR.OutputDir)\ScanReport.txt" -Value "$($Scan.Name) ($($Scan.status))"
IF ($Scan.status -eq "Completed") {
$File = (Invoke-RestMethod -URI "$baseurl/scans/$($Scan.ID)/export" -ContentType $ContentType -Headers $Header -Method $POSTMethod -Body $(convertto-json (New-Object PSObject -Property #{format = "$Format"}))).file
[Void]$StatusArray.Add(
[pscustomobject]#{
ScanName = $scan.name
StatusUri = $baseurl + "/scans" + "/" + $Scan.id + "/export/" + "$file" + "/status"
DownloadUri = $baseurl + "/scans" + "/" + $Scan.id + "/export/" + "$file" + "/download"
}
)
}
}
#------------------Check Status of Export requests-------------------------------------------------
While ($StatusArray.StatusUri.count -GT $StatusURI.Count) {
ForEach ($ScanStatus in $StatusArray.StatusURI) {
IF ((Invoke-RestMethod -Uri $ScanStatus -ContentType $ContentType -Headers $Header -Method $GETMethod).status -EQ "Ready") {
if ($StatusURI -notcontains $ScanStatus) {
Write-Host "Adding $ScanStatus"
[void]$StatusURI.Add($ScanStatus)
}
} Else {
Write-nLog -Type "Info" -Message "Not all scans complete. ($($GNR.StatusURI.Count)/$($StatusArray.StatusUri.count)"
Start-Sleep -s 5
}
}
}
#------------------Download the Reports------------------------------------------------------------
$ExportUri = $StatusArray.DownloadUri
$outputs = $StatusArray.ScanName
foreach ($i in 0..($ExportUri.Count - 1)) {
Write-nLog -Type Info -Message "Exporting Report: $($outputs[$i])"
Invoke-WebRequest -Uri $ExportUri[$i] -ContentType $ContentType -Headers $Header -Method $GETMethod -OutFile "$($GNR.OutputDir)\$($outputs[$i]).$Format"
}
}
#------------------Script END----------------------------------------------------------------------
There are several additional parameters you can set on the POST /scans/{id}/export endpoint. The important one missed here is chapters which accepts a semi-colon delimted list of the desired content sections. This must be set for exports of pdf or html types, otherwise you get an empty result.
For example, to get the executive summary, in addition to format of html/pdf/csv etc, set chapters to vuln_hosts_summary. The other available options are:
vuln_by_host
compliance_exec
remediations
vuln_by_plugin
compliance
Hopefully this helps the next person trying to debug empty Nessus API exports too!
For full API docs for your version check out https://{YOUR_NESSUS_INSTALL}/api

Invoke-WebRequest in an own function with arguments

I am trying to run an Invoke-WebRequest command on PowerShell Core 6.3 on a Ubuntu system with the following settings to create a forwarder:
$userLocalMailPart = "user"
$userGlobalMailPart = "Hotmail.com"
$address = "Some Address"
$pair = $login + ":" + $password
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)
$basicAuthValue = "Basic $base64"
$url = "https://" + $server + ":" + $port + "/Domain/ObjectList.html"
$headers = #{"Authorization" = $basicAuthValue;
"Referer" = $url
}
$body = #{
"NewForwarderName" = $userLocalMailPart ;
"NewForwarderAddress" = $address;
"CreateForwarder" = "Create Forwarder"
}
$result = Invoke-WebRequest `
-Uri $url `
-SkipCertificateCheck `
-Method 'POST' `
-Headers $headers `
-Body $body
This runs perfectly. No error and the forwarder is successfully created.
Now I want to use a function like this:
[Web]::requestHeaderContent($url, $headers, (ConvertTo-Json($body)), 'POST')
[object] static requestHeaderContent([String] $url, [hashtable] $headers, [Object] $body, [String] $meth) {
Invoke-WebRequest `
-Uri $url `
-SkipCertificateCheck `
-Method $meth `
-Headers $headers `
-Body $body
}
The variables like headers and body are the same. I also tried it with ConvertTo-Json but it just don’t work. I also receive a Success 200 Code but the forwarder is not created. I guess my problem are the parameters. Any idea what I am doing wrong?
Thanks
Stephan

PowerShell Slack Function

I am trying to write a PowerShell function to call a Slack webhook. I took the function from a Reddit post, but the function seems to be failing with a parse error. Also I removed the webhook.
Here is the code:
function Send-SlackMessage {
Param (
[Parameter(Mandatory=$true, Position=0)]$Text,
$Url = "https://hooks.slack.com/services/xxxxx",
# Parameters below are optional and will fall back to the default
$Username = "XXXXXXX",
$Channel = "XXXXXXX",
$Emoji = "XXXXXX"
)
$body = #{ text=$Text; channel=$Channel; username=$Username; icon_emoji=$Emoji } | ConvertTo-Json
Invoke-WebRequest -Method Post -Uri $Url -Body $body
}
and the error:
At line:12 char:67
+ ... y = #{ text=$Text; channel=$Channel; username=$Username; icon_emoji=$ ...
+
Missing '=' #{ text=$Text; channel=$Channel; username=$Username; icon_emoji=$ ...
The hash literal was incommplete.
+ CategoryInfo :ParserError (:) [], ParentContainsErrorRecordException
+ FillyQualifiedErrorId : MissingEqualsInHashLiteral
Where's the function name? You are missing a comma after the 2nd param, and have an extra comma after the last param.
function whatever ()
{
param (
[Parameter(Mandatory=$true, Position=0)]$Text,
$Url="https://hooks.slack.com/services/xxxxx",
# Parameters below are optional and will fall back to the default
$Username = "XXXXXXX",
$Channel = "XXXXXXX",
$Emoji = "XXXXXX"
)
$body = #{ text=$Text; channel=$Channel; username=$Username; icon_emoji=$Emoji } | ConvertTo-Json
Invoke-WebRequest -Method Post -Uri $Url -Body $body
}

Foreach and If linked issue

Here after my code for which I have an issue:
#Delivery Groups Information
$dgroup = Invoke-RestMethod -Uri "https://${XMS}:4443/xenmobile/api/v1/deliverygroups/filter" -Body '{}' -Headers $headers -Method Post
$new = 0
$count = $dgroup.dglistdata.dglist.length
for ($v=0; $v -lt $count; $v++) {
foreach ($dglistdata in $dgroup) {
Write-Host $dglistdata.dglistdata.dglist[$new].name
$new++
}
}
$Host.UI.RawUI.ForegroundColor = "white"
$dgroup = Read-Host -Prompt "Please provide Delivery Group Name for which notification will be sent"
$message = Read-Host -Prompt "Please provide the message to be sent"
#Devices
$devices = Invoke-RestMethod -Uri "https://${XMS}:4443/xenmobile/api/v1/device/filter" -Body '{}' -Headers $headers -Method Post
foreach ($device in $devices.filteredDevicesDataList) {
Write-Output $device.id >$null
Write-Output $device.platform >$null
}
foreach ($device in $devices.filteredDevicesDataList) {
$url = "https://${XMS}:4443/xenmobile/api/v1/device/" + $device.id + "/deliverygroups"
$global:dg = Invoke-RestMethod -Uri $url -Headers $headers -Method Get
foreach($deliverygroups in $dg) {
Write-Output $dg.deliveryGroups.name >$null
}
}
foreach ($device in $devices.filteredDevicesDataList) {
if ($dg.deliveryGroups.name -match $dgroup) {
Write-Host $device.devicemodel
Send-Notification
} else {
$dgroup = 0
}
}
Info:
The main object of the code is to send notification to devices based on which group devices are member of.
Example:
iPad member of "DG 2"
iPhone member of "DG 1"
Result:
Do I miss something there?
In fact, I think I found the solution, I need to include my loop for notification in the other loop as:
foreach($device in $devices.filteredDevicesDataList)
{
$url = "https://${XMS}:4443/xenmobile/api/v1/device/" + $device.id + "/deliverygroups"
$Global:dg=Invoke-RestMethod -Uri $url -Headers $headers -Method Get
foreach($deliverygroups in $dg)
{
write-output $dg.deliveryGroups.name >$Null
If($dg.deliveryGroups.name -match $dgroup)
{
write-host $device.devicemodel
Send-notification
}
}
}