How to pass optional parameters in PowerShell? - powershell

This is a wrapper function for Invoke-WebRequest (I have removed a lot of extra functionality to keep the noise down)
function Invoke-SERVERAPI($apiFolder, $adminCredentials, [ValidateSet("GET","POST","PUT","DELETE")] $HTTPmethod, $contentType, $body, $verbose)
{
$resp1HTTPCode= 'Not set'
try
{
if ( ($HTTPmethod -eq 'GET') -or ($HTTPmethod -eq 'DELETE'))
{
$resp1 = Invoke-WebRequest -Uri $apiFolder -Method $HTTPmethod -Credential $adminCredentials -ContentType $contentType -ErrorAction SilentlyContinue -Verbose:$verbose
}
else
{
$resp1 = Invoke-WebRequest -Uri $apiFolder -Body $body -Method $HTTPmethod -Credential $adminCredentials -ContentType $contentType -ErrorAction SilentlyContinue -Verbose:$verbose
}
$resp1HTTPCode = $resp1.StatusCode
}
catch [Exception]
{
$resp1HTTPCode = $_.Exception.Response.StatusCode.Value__
}
return $resp1HTTPCode
}
I need to pass -body parameter on verb POST and PUT but don't pass it in GET and DELETE. I managed to do it with an IF/ELSE.
Is there a better way to achieve this in PowerShell like I did in the switch parameter -Verbose?

Yes, it involves forming a hashtable with parameters and using that instead or in addition of parameter list, aka splatting. In your case, you do like this:
try
{
$ifbody=#{}
if ( ($HTTPmethod -eq 'PUT') -or ($HTTPmethod -eq 'POST'))
{
$ifbody."Body"=$body
}
$resp1 = Invoke-WebRequest -Uri $apiFolder #ifbody -Method $HTTPmethod -Credential $adminCredentials -ContentType $contentType -ErrorAction SilentlyContinue -Verbose:$verbose
$resp1HTTPCode = $resp1.StatusCode
}
The #ifbody reverts the hashtable into -key=value -key2=value2... sequence of parameters to a cmdlet or a function.

You should have a look at ParameterSetName in about_Functions_Advanced_Parameters. It can help you to differentiate different sets of parameters.

Related

How do I get return values from Invoke-WebRequest

I am trying to figure out how to get results from a Invoke-WebRequest to download a file from the internet. I wrote a function but it doesn't seem to get the results. StatusCode and StatusDescription never change even if it works:
function DownloadUpgradeTool()
{
##download upgrade tools from MS
Write-Host "Downloading Upgrade Tool from MS."
$url = "https://download.microsoft.com/download/3/d/c/3dcc9642-d3a0-459c-86fd-128f5a0c3cc5/Windows10Upgrade9252.exe"
$output = "c:\Temp\upgrade.exe"
$StatusCode = 1
$StatusDescription = "Error downloading file"
try
{
$response = Invoke-WebRequest -Uri $url -OutFile $output -ErrorAction Stop
}
catch
{
$StatusCode = $_.Exception.Response.StatusCode.value__
$StatusDescription = $_.Exception.Response.StatusDescription
}
Write-Host " Download Status Code: " $StatusCode
Write-Host "Download Status Description: " $StatusDescription
if ($StatusCode)
{
return $False
}
else
{
return $True
}
}
function RunUpgrade()
{
##run silent upgrade
##c:\Temp\upgrade.exe /quietinstall /skipeula /auto upgrade /copylogs c:\Temp
}
You get the status code when you evaluate the object that is returned by Invoke-WebRequest
$response = Invoke-WebRequest -URI $url -OutFile $output -ErrorAction Stop
Write-Host $response.StatusCode
$r = Invoke-WebRequest -URI https://stackoverflow.com/questions/20259251/
Write-Host $r.StatusCode
https://davidhamann.de/2019/04/12/powershell-invoke-webrequest-by-example/
Powershell implements the OOP (object-oriented programming) design paradigms and semantics i.e. when writing object-oriented code each class has to have a constructor (new()) and a destructor and it should have get() and set() methods to access (read and write) the fields (or attributes) of a class. In ps this is straighlty implemented
Return values of cmdlets are often objects (in the sense of OOP) and you can access the fields of the objects to gather the data ...
It is also possible to use object-oriented design patterns in ps scripting https://dfinke.github.io/powershell,%20design%20patterns/2018/04/13/PowerShell-And-Design-Patterns.html
You need to include -PassThru when you are using -OutFile if you want something in your $response variable.
The exact purpose of PassThru from the PowerShell docs.
Indicates that the cmdlet returns the results, in addition to writing
them to a file. This parameter is valid only when the OutFile
parameter is also used in the command.
For example,
$response = Invoke-WebRequest -Uri 'www.google.com'
if ( $response.StatusCode -eq 200 )
{
#This bit runs for HTTP success
}
$response = Invoke-WebRequest -Uri 'www.google.com' -OutFile 'googleHome.html'
if ( $response.StatusCode -eq 200 )
{
#This never runs as $response never has a value even though the googleHome.html file gets created
}
$response = Invoke-WebRequest -Uri 'www.google.com' -OutFile 'googleHome.html' -PassThru
if ( $response.StatusCode -eq 200 )
{
#This bit runs for HTTP success and the file gets created
}

Powershell Script does not wait for the response from Invoke-RestMethod cmdlet

I am new to powershell. I have a powershell script which basically makes an REST API request and gets back a JSON response. But I have an issue with the Invoke-RestMethod cmdlet. I mean the script sometimes doesn't wait for the response and continues with the next line of code.
Is there some trick in powershell which asks the code to wait for the API response and then run the remaining code. Below is my code
if ($EventID -eq '4726') {
$ad_user = "$TargetUsername#avayaaws.int"
$jsonbody = #{
"setupName" = "avayadev-or"
"instanceName" = "000_JumpServer_DMZ"
"command" = "$Command"
"parameters" = #{
"mobile" = "$getMobileAttr"
"ad_user"="$ad_user"
"label" ="$environment"
}
"eventToken" = "optional"
} | ConvertTo-Json
#$response = Start-Job {
# Invoke-RestMethod -Uri $uri -Method $verb -Body $jsonbody -Headers $header
#} | wait-job | receive-job
#$response = Invoke-RestMethod -Uri $uri -Method $verb -Body $jsonbody -Headers $header
$response = Invoke-WebRequest -Uri $uri -Method $verb -Body $jsonbody -Headers $header
$EventTrigger = $translEvent4726
Write-Output "Response is: $response"
PushLogs -transaction $EventTrigger -adUser $ad_user -transResult $response
}
I have tried many ways like using Out-Null and Wait-Job, Recieve-Job but couldn't get it to work. Any help is much appreciated.

Pass entire hashtable to Invoke-Expression

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)

How to use Get-AnsibleParam, built-in function in Ansible

As the tile said, i would like to know the way to use this Get-AnsibleParam function. More specific, how to apply Get-AnsibleParam in this case:
i have a YAML file that contained some variables
---
type: taskWindows
actions:
abortActions: []
emailNotifications: []
setVariableActions: []
snmpNotifications: []
systemOperations: []
agent: test
name: "Test"
summary: "Test"
...
I have a Powershell Script:
function Stonebrach.Connect {
param(
[Parameter(Mandatory=$true)]
$Sb_uri,
[Parameter(Mandatory=$true)]
$Auth_filename,
[Parameter(Mandatory=$true)]
$Method,
[Parameter(Mandatory=$false)]
$Body
)
$basicAuth=Get-Content "C:\$auth_filename"
$headers = #{
"Authorization" = $basicAuth
"Content-Type"="application/json"
}
$RSP=$null
try{
if ( $body -eq $null ) {
$response = invoke-restmethod -Uri $Sb_uri -Method $Method -Headers $headers
}
else {
$response = invoke-restmethod -Uri $sb_uri -Method $method -Headers $headers -Body **$body**
}
return $response
}
catch{
$RSP = $_.Exception.Message
return $RSP
}
}
function Stonebranch.Create.Task.Windows {
param(
[Parameter(Mandatory=$true)]
$Sb_base_uri,
[Parameter(Mandatory=$true)]
$Auth_filename,
[Parameter(Mandatory=$true)]
$Method,
[Parameter(Mandatory=$true)]
$Body
)
Stonebrach.Connect -SB_uri $Sb_base_uri/task -Auth_filename
$Auth_filename -Method $Method -Body $Body
}
Can i use Get-AnsibleParam to get the variables from YAML file (all of them) into the $body in the PS script in this case? I tried to read the documentation, but it is not very clear how to use the function:
https://code.vt.edu/nis-ansible-roles/upstream-ansible/blob/2a751e1753c1fe675ba3d0b1dbc9939c9253ea70/lib/ansible/module_utils/powershell.ps1
Please help. Thanks
Get-AnsibleParam is used to access specific parameter passed to a module. You can't use it to get all variables from your playbooks.
Take a look at win_ping for example usage:
$params = Parse-Args $args -supports_check_mode $true
$data = Get-AnsibleParam -obj $params -name "data" -type "str" -default "pong"
if ($data -eq "crash") {
throw "boom"
}
In this case win_ping can accept parameter named data of type string with default value pong.

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