Trying to get Powershell to work with an API - powershell

I am trying to write a Powershell script to work with a Microsoft API, and being new to this, I have come across an error that I cannot seem to get past.
Error:
Cannot find an overload for "UpdateDeviceValues" and the argument count: "3".
At C:\PowerShellScripts\API-UpdateDeviceValues-ICC.ps1:13 char:37
+ $accounts = $pmws.UpdateDeviceValues <<<< ('18B81F1FD790','DisableICC','True');
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
From my searches, it seems that the API is requiring three values to be sent to it, but that is what I am sending.
My code:
$pmws = New-Object PrincipalManagement;
$pmws.Credentials = $cred;
$pmws.url = "http://172.31.9.2/bss/PrincipalManagement.asmx";
$accounts = $pmws.UpdateDeviceValues('18B81F1FD790','DisableICC','True');
$accounts
Any help with this error would be much appreciated. I am rather new to this, getting Powershell to work with APIs.
This is the SOAP 1.1 example for the API.
POST /bss/PrincipalManagement.asmx HTTP/1.1
Host: 172.31.9.2
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://www.microsoft.com/iptv/bss/UpdateDeviceValues"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body>
<UpdateDeviceValues xmlns="http://www.microsoft.com/iptv/bss">
<deviceExternalID>string</deviceExternalID>
<deviceValues>
<DeviceValue Key="string" Value="string" />
<DeviceValue Key="string" Value="string" />
</deviceValues>
</UpdateDeviceValues>
</soap:Body>
</soap:Envelope>
I can run this in PowerShell:
> $pmws | Get-Member
I get a long list of the APIs, and in that list is the one I am trying to use...
TypeName: PrincipalManagement
Name - MemberType - Definition
UpdateDeviceValues - Method - System.Void UpdateDeviceValues(string deviceExternalID, DeviceValue[] deviceValues)
Much thanks to Chamele0n! Answer found!
I was missing how to properly format the parameters when calling the method.
This works!
$Account = "18B81F1FD790"
$deviceValue = New-Object ($ns + ".DeviceValue")
$deviceValue.Key = "DisableICC"
$deviceValue.Value = "True"
$accounts = $pmws.UpdateDeviceValues($Account, $deviceValue)
Thanks,
Jason...

Related

Unreliable HTTP status code when using the HEAD method

I have a PowerShell script to verify the existence of many (half a million) files in a web server. Some of these files are large (gigabytes) and I don't wan't to transfer them, so I've been using the HTTP HEAD method.
The problem is, today I found that the HTTP status code returned from the HEAD method may be different from the status returned by GET (which is always the correct one):
Using HEAD:
> Invoke-WebRequest -Method head http://example.com/82833749.mbtiles
StatusCode : 200
StatusDescription : OK
Content : {}
RawContent : HTTP/1.1 200 OK
Content-MD5: LwIBDR5QRptNAE2Hdpu+aw==
x-ms-request-id: 774da565-101e-0053-7903-38e136000000
x-ms-version: 2009-09-19
x-ms-lease-status: unlocked
x-ms-blob-type: BlockBlob
Connect...
Headers : {[Content-MD5, LwIBDR5QRptNAE2Hdpu+aw==], [x-ms-request-id,
774da565-101e-0053-7903-38e136000000], [x-ms-version, 2009-09-19], [x-ms-lease-status,
unlocked]...}
RawContentLength : 0
With GET:
> Invoke-WebRequest http://example.com/82833749.mbtiles
Invoke-WebRequest : BlobNotFoundThe specified blob does not exist. RequestId:dede785b-b01e-0091-4b08-38a7b0000000
Time:2022-03-15T01:02:36.7883837Z
No linha:1 caractere:1
+ Invoke-WebRequest http://example.com/82833749.mbtiles
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
> $Error[0].Exception.Response.StatusCode.value__
404
Right after running the GET request, the HEAD request will return the correct status code, making it more confusing to understand what is going on.

AWS Cloudfront files' invalidation via REST API and Powershell

I'm trying to write a PowerShell script to invalidate an object of AWS Cloudfront distribution (just a specific file), and not sure how to produce the "signed URL" they're asking for.
My code so far is:
$authPref = "AWS4-HMAC-SHA256"
$AWSAccessKey = "xxx"
$AWSSecretKey = "xxx"
$awsDateOnly = (Get-Date).AddHours(-3).ToString("yyyyMMdd")
$awsRegion = "us-east-1"
$awsServiceName = "cloudfront"
$awsRequestType = "aws4_request"
$stringToSign = $authPref + " " + $awsCallerReference + " " + $awsDateOnly + "/" + $awsRegion + "/" + $awsServiceName + "/" + $awsRequestType + " SOME_STRING_NOT_SURE_WHAT"
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($stringToSign)
$awsHMAC = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($AWSSecretKey))
$awsHMAC = [Convert]::ToBase64String($awsHMAC)
$awsSignedToken = $authPref + " Credential=" + $AWSAccessKey + "/" + $awsDateOnly + "/" + $awsRegion + "/" + $awsServiceName + "/" + $awsRequestType + ", SignedHeaders=content-type;host;x-amz-date, Signature=" + $awsHMAC
#POST /2017-03-25/distribution/$awsDistributionID/invalidation HTTP/1.1
$awsDistributionID = "xxx"
$awsCallerReference = (Get-Date).AddHours(-3).ToString("yyyyMMdd'T'HHmmss'Z'")
$invalidateObjectXML = #"
<?xml version="1.0" encoding="UTF-8"?>
<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2017-03-25/">
<CallerReference>$awsCallerReference</CallerReference>
<Paths>
<Items>
<Path>/</Path>
</Items>
<Quantity>1</Quantity>
</Paths>
</InvalidationBatch>
"#
[xml]$invalidateObjectXML = $invalidateObjectXML
$awsCFuri = "https://cloudfront.amazonaws.com/2017-03-25/distribution/$awsDistributionID/invalidation"
Invoke-WebRequest -Method POST `
-Uri $awsCFuri `
-Headers #{"content-type"="text/xml";
"x-amz-date"="$awsCallerReference";
"authorization"="$awsSignedToken";
"host"="cloudfront.amazonaws.com"} `
-Body $invalidateObjectXML
The response is:
<ErrorResponse xmlns="http://cloudfront.amazonaws.com/doc/2017-03-25/"><Error><Type>Sender</Type><Code>SignatureDoesNotMatch</Code><Message>The request
signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation
for details.
The Canonical String for this request should have been
'POST
/2017-03-25/distribution/blabla/invalidation
content-type:text/xml
host:cloudfront.amazonaws.com
x-amz-date:20170503T203650Z
content-type;host;x-amz-date
blabla'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20170503T203650Z
20170503/us-east-1/cloudfront/aws4_request
blabla'
</Message></Error><RequestId>123-123</RequestId></ErrorResponse>
At line:1 char:1
So obviously I'm doing something wrong with the signed URL string that I do, but what it is?
Couldn't find any examples on the internet (not AWS docs nor any other blog) that demonstrates it in Powershell.
Thanks
AWS has developed a PowerShell module to interact with the AWS API called the AWS Tools for PowerShell. This module specifically handles request building and signing for you, so this method of calling to the raw API becomes unnecessary.
You can use this specifically to invalidate objects in a CloudFront distribution with the New-CFInvalidation cmdlet. Write the paths you want to invalidate to the Paths_Item parameter.
Signature:
New-CFInvalidation
-DistributionId <String>
-InvalidationBatch_CallerReference <String>
-Paths_Item <String[]>
-Paths_Quantity <Int32>
-Force <SwitchParameter>
Further Reading
AWS Documentation - Invalidating Objects and Displaying Information about Invalidations
AWS Documentation - New-CFInvalidation Cmdlet

Why Error "System.String does not contain a method named 'AppendChild'"

Why does this PowerShell Script not work:
[xml]$xml = '<products></products>'
$newproduct = $xml.CreateElement('product')
$attr = $xml.CreateAttribute('code')
$attr.Value = 'id1'
$newproduct.Attributes.Append($attr)
$products = $xml.products
$products.AppendChild($newproduct)
Error is
Method invocation failed because [System.String] does not contain a method named 'AppendChild'.
At line:1 char:1
+ $products.AppendChild($newproduct)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
If I replace
$products = $xml.products
by
$products = $xml.SelectSingleNode('//products')
it will work, but I'd like to know why first syntax does not work because it is illogical for me. $xml.products should be a valid XML object and thus provide the method AppendChild().
$xml.products does not reference the products node itself, but the contents of the products node. Since products is empty, it evaluates to an empty string.
To get to the products node you could also use:
$Products = $xml.FirstChild
$Products.AppendChild($newproduct)
or, more specifically:
$Products = $xml.ChildNodes.Where({$_.Name -eq "products"}) |Select-Object -First 1
$Products.AppendChild($newproduct)
But SelectSingleNode() will probably serve you just fine
$xml.configuration.SelectSingleNode("connectionStrings").AppendChild($newnode)
would work fine, but an interesting & dirty hack: given this empty node:
<connectionStrings>
</connectionStrings>
the script
$xml.configuration.connectionStrings.AppendChild($newnode)
gives a "Method invocation failed because [System.String] does not contain a method named 'AppendChild'" error
but this:
<connectionStrings>
<!-- random comment -->
</connectionStrings>
works perfectly fine

Powershell web request - 400 Bad Request

Im building a powershell script to execute commands on a REST-like server, I am using powershell to POST XML data to the webservice. Everything is working perfectly except when the "application" produces an error. The application replies back with details in xml and also gives me a Web Request Error (in this case 400) which is when i have problems. Whenever i get a Web Request Error that is not 200 the commands throw an error which screws up how i handle the error.
This is an example of the response im receiving (using fiddler):
HTTP/1.1 400 Bad Request
Date: Thu, 29 Nov 2012 04:29:48 GMT
Content-Type: text/xml;charset=ISO-8859-1
Connection: close
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Status>
<operation>Add Network</operation>
<result>ERROR</result>
<resultCode>REASON_210</resultCode>
</Status>
This is the error i receive in powershell:
Exception calling "UploadData" with "3" argument(s): "The remote server returned an error: (400) Bad Request."
At C:\Scripting\test.ps1:47 char:34
+ $response = $query.UploadData <<<< ($url,"POST",$byteArray)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Exception calling "GetString" with "1" argument(s): "Array cannot be null.
Parameter name: bytes"
At C:\Scripting\test.ps1:48 char:62
+ $stringresponse = [System.Text.Encoding]::ASCII.GetString <<<< ($response)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
I dug further into the error and this is as close as i could get to retrieving the content of the response (the XML).
I thought I might be able to pull it out from this..
$error[1].exception.InnerException.Data
But it is empty :(
PS C:\Scripting> $error[1].exception.InnerException | fl -force
Status : ProtocolError
Response : System.Net.HttpWebResponse
Message : The remote server returned an error: (400) Bad Request.
Data : {}
InnerException :
TargetSite : Byte[] UploadDataInternal(System.Uri, System.String, Byte[], System.Net.WebRequest ByRef)
StackTrace : at System.Net.WebClient.UploadDataInternal(Uri address, String method, Byte[] data, WebRequest& request)
at System.Net.WebClient.UploadData(Uri address, String method, Byte[] data)
at System.Net.WebClient.UploadData(String address, String method, Byte[] data)
at UploadData(Object , Object[] )
at System.Management.Automation.MethodInformation.Invoke(Object target, Object[] arguments)
at System.Management.Automation.DotNetAdapter.AuxiliaryMethodInvoke(Object target, Object[] arguments, MethodInformation methodInformation, Object[]
originalArguments)
HelpLink :
Source : System
PS C:\Scripting> $error[1].exception.InnerException.response | fl
IsMutuallyAuthenticated : False
Cookies : {}
Headers : {Connection, Transfer-Encoding, Content-Type, Date}
ContentLength : -1
ContentEncoding :
ContentType : text/xml;charset=ISO-8859-1
CharacterSet : ISO-8859-1
Server :
LastModified : 29/11/12 4:04:19 PM
StatusCode : BadRequest
StatusDescription : Bad Request
ProtocolVersion : 1.1
ResponseUri : https://api-au.dimensiondata.com/oec/0.9/cdefb891-1552-4008-8638-7393eda8d7e1/network
Method : POST
IsFromCache : False
Here is my powershell code that I am using:
function send_web ($url,$data) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$query = new-object system.net.WebClient
$auth = 'Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($username+":"+$password ))
$query.Headers.Add('Authorization', $auth )
[byte[]]$byteArray = [System.Text.Encoding]::ASCII.GetBytes($data)
$query.Headers.Add("ContentType","application/xml")
$response = $query.UploadData($url,"POST",$byteArray)
$stringresponse = [System.Text.Encoding]::ASCII.GetString($response)
return $stringresponse
}
This seems like it shouldn't be a hard thing to do any help would be much appreciated :)
Richard

google apps script soap client without wsdl

I am attempting to access the Numara Footprints web services API from google apps script. There is no wsdl for the web service, but the methods are well documented. All of the examples of using Google Apps Script Soap Service assume the existence of a wsdl file, which makes it a non-starter for my purposes. So I am trying to use UrlFetchApp instead. By using the sample php code that Numara Footprints provides, I established what the request should look like, and wrote the following code in google apps script:
function sendHttpPost() {
var payload= '<?xml version="1.0" encoding="utf-8"?>' +
'<SOAP-ENV:Envelope'+
'xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'+
'xmlns:ns1="MRWebServices"'+
'xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
'xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"' +
'SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'+
'<SOAP-ENV:Body>'+
'<ns1:MRWebServices__search>'+
'<param0 xsi:type="xsd:string">ACCOUNT NAME</param0>'+
'<param1 xsi:type="xsd:string">PASSWORD</param1>'+
'<param2 xsi:type="xsd:string"></param2>'+
'<param3 xsi:type="xsd:string">'+
"SELECT * from MASTER1 where mrid='16888'</param3>"+
'</ns1:MRWebServices__search>'+
'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';
var headers =
{
"SOAPAction" :encodeURIComponent("MRWebServices#MRWebServices__search")
};
var options =
{
"contentType": "text/xml; charset=utf-8",
"method" : "post",
"headers" : headers,
"payload" : payload
};
UrlFetchApp.fetch("http://FOOTPRINTS SERVER/MRcgi/MRWebServices.pl", options);
}
This code contacts the server successfully and gets the following error message returned:
Request failed for http://FOOTPRINTS SERVER/MRcgi/MRWebServices.pl
returned code 500. Server response:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>Application failed during request deserialization:
not well-formed (invalid token) at line 1, column 70, byte 70 at
C:/FootPrints/bin/Perl/lib/XML/Parser.pm line 187 </faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> (line 40)
I don't know of any way to get more detailed error messages, and I don't know what the problem is. The xml that I am sending is copied directly from the PHP code that works perfectly.
I'm wondering if anyone either:
can see a problem with the google apps script code above or
can tell me how to use the Google Apps Script soap service when I don't have a wsdl file or
knows of a way to get a web service to return more error details.
Thanks in advance. I've looked everywhere that I can think of but not found an answer.
The payload var in the above code needed spaces between the XML elements. Corrected code (note spaces at the end of each line):
var payload= '<?xml version="1.0" encoding="utf-8"?> ' +
'<SOAP-ENV:Envelope '+
'xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" '+
'xmlns:ns1="MRWebServices" '+
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" ' +
'SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> '+
'<SOAP-ENV:Body> '+
'<ns1:MRWebServices__search> '+
'<param0 xsi:type="xsd:string">ACCOUNT NAME</param0> '+
'<param1 xsi:type="xsd:string">PASSWORD</param1> '+
'<param2 xsi:type="xsd:string"></param2> '+
'<param3 xsi:type="xsd:string"> '+
"SELECT * from MASTER22 where mrid='16888'</param3> "+
'</ns1:MRWebServices__search> '+
'</SOAP-ENV:Body> '+
'</SOAP-ENV:Envelope>';
Also, in the headers, I did not need to use encodeURIComponent(). Corrected code:
var headers =
{
"SOAPAction" :"MRWebServices#MRWebServices__search"
};