I'm creating a solution to get info from our Azure AD tenant. I'm using powershell Invoke-Webrequest and OAuth 2.0 client credential grant to get the info. When I use a client secret my script is working fine but I want to make it more secure and use a self-signed certificate.
I use the following command to create a new certificate on my local pc:
$cert = New-SelfSignedCertificate -HashAlgorithm "SHA256" -Subject "CN=******" -CertStoreLocation "Cert:\Currentuser\My" -KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddYears(5)
After creating I export this certificate to .cer using mmc and upload it to the Azure AD app.
Image from uploaded certificate
I also creating a JSON Web Token using the following script:
############################################
## Variable
############################################
$appEndPoint = "https://login.microsoftonline.com/************/oauth2/token"
$appClientID = "*******************************"
$exportPath = [Environment]::GetFolderPath("desktop")
$guid = [guid]::NewGuid()
############################################
## JWT Token starttime/endtime
############################################
$jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()
$jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()
###########################################
##Decoded Json JWT Token
###########################################
$jwtID = $guid.Guid
$decJwtHeader = '{"alg":"RS256","typ":"JWT","x5t":"' + [System.Convert]::ToBase64String(($cert.GetCertHash())) + '"}'
#$decJwtPayLoad = '{"ver":"2.0","aud":"'+ $appEndPoint + '","exp":' + $jwtEndTimeUnix + ',"iss":"' + $appClientID + '","jti":"' + $jwtID + '","nbf":' + $jwtStartTimeUnix + ',"sub":"' + $appClientID +'"}'
$decJwtPayLoad = '{
"aud":"' + $appEndPoint + '"
, "exp":"' + $jwtEndTimeUnix + '"
, "iss":"' + $appClientID + '"
, "jti":"' + $jwtID + '"
, "nbf":"' + $jwtStartTimeUnix + '"
, "sub":"' + $appClientID + '"
}'
##########################################
##Encode Json JWT Token
##########################################
$encJwtHeaderBytes = [system.text.encoding]::UTF8.GetBytes($decJwtHeader)
$encHeader = [system.convert]::ToBase64String($encJwtHeaderBytes).Split('=')[0].Replace('+', '-').Replace('/', '_')
$encJwtPayLoadBytes = [system.text.encoding]::UTF8.GetBytes($decJwtPayLoad)
$encPayLoad = [system.convert]::ToBase64String($encJwtPayLoadBytes).Split('=')[0].Replace('+', '-').Replace('/', '_')
$jwtToken = $encHeader + '.' + $encPayLoad
$toSign = [system.text.encoding]::UTF8.GetBytes($jwtToken)
#########################################
##Sign JWT Token
#########################################
$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = "2.16.840.1.101.3.4.2.1"
$dataBytes = [System.Text.Encoding]::UTF8.GetBytes($toSign)
$hashBytes = $HashAlgo.ComputeHash($dataBytes)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
$sig = [System.Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '='
#$sig = [system.convert]::ToBase64String($cert.PrivateKey.SignData($toSign,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) -replace '\+','-' -replace '/','_' -replace '='
$jwtToken = $jwtToken + '.' + $sig
$exportPath = $exportPath + "\jwtToken.txt"
$jwtToken | Out-File $exportPath
This JSON Web Token is the client assertion in my Invoke-Webrequest
#################################################################
## ENKEL VOOR TESTING, NIET IN AMP GEBRUIKEN ####################
#################################################################
$tenantid = '**********'
$subscriptionid = '**********'
$clientid = '**********'
$client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
$client_assertion = '********'
##################################################################
##################################################################
##################################################################
$return = Invoke-Command -ScriptBlock {
param($tenantid,$subscriptionid,$clientid,$client_assertion_type,$client_assertion)
Add-Type -AssemblyName System.Web
$enc_client_assertion_type = [System.Web.HttpUtility]::UrlEncode($client_assertion_type)
$encScope = [System.Web.HttpUtility]::UrlEncode('https://management.azure.com/.default')
$enc_client_assertion = [System.Web.HttpUtility]::UrlEncode($client_assertion)
$body = "scope=$encScope&client_id=$clientid&client_assertion_type=$enc_client_assertion_type&client_assertion=$enc_client_assertion&grant_type=client_credentials"
$auth = Invoke-WebRequest "https://login.microsoftonline.com/$tenantid/oauth2/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
$token = ($auth | ConvertFrom-Json).access_token
$headers = #{
'Authorization'="Bearer $($token)"
}
$data = Invoke-WebRequest "https://management.azure.com/subscriptions/$subscriptionid/providers/Microsoft.Advisor/recommendations?api-version=2017-04-19" -Method GET -Headers $headers -UseBasicParsing
New-Object PSObject -Property #{
content=$data.content
}
} -ArgumentList $tenantid,$subscriptionid,$clientid,$client_assertion_type,$client_assertion
$content = $return.content
Write-Host $content
I'm receiving the following error but can't find out why. Anyone an idea?
Invoke-WebRequest : {"error":"invalid_client","error_description":"AADSTS700027: Client assertion contains an invalid signature. [Reason - The provided signature value did not match the expected signature value.,
Thumbprint of key used by client: '******************', Found key 'Start=10/14/2019 09:02:58, End=10/14/2024 09:12:59', Please visit 'https://developer.microsoft.com/en-us/graph/graph-explorer'
and query for 'https://graph.microsoft.com/beta/applications/***********' to see configured keys]\r\nTrace ID: 085758a5-7470-4a3d-91ba-6f98518e7100\r\nCorrelation ID:
1ab6c7d2-4e46-4e7b-a56f-42720a24286a\r\nTimestamp: 2019-10-14 09:43:15Z","error_codes":[700027],"timestamp":"2019-10-14
09:43:15Z","trace_id":"085758a5-7470-4a3d-91ba-6f98518e7100","correlation_id":"1ab6c7d2-4e46-4e7b-a56f-42720a24286a","error_uri":"https://login.microsoftonline.com/error?code=700027"}
You are almost right!
The only problem is that you forget using your own certificate:
$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = "2.16.840.1.101.3.4.2.1"
$dataBytes = [System.Text.Encoding]::UTF8.GetBytes($toSign)
$hashBytes = $HashAlgo.ComputeHash($dataBytes)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
The right way is that:
#Get cert from your cert store. Please make sure that the private key is exportable
$Cert = Get-ChildItem -Path cert:\Currentuser\My | Where {$_.Subject -eq "CN=AADApplicationWithCert"}
$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = [System.Security.Cryptography.CryptoConfig]::MapNameToOID("SHA256");
#Use your private key
$RSACryptoSP.FromXmlString($Cert.PrivateKey.ToXmlString($true))
$hashBytes = $HashAlgo.ComputeHash($toSign)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
$signedBytes = [Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '='
My whole sample:
$appEndPoint = "https://login.microsoftonline.com/hanxia.onmicrosoft.com/oauth2/token"
$appClientID = "dc175b96-c196-43cf-aa0b-ea03e56da5e7"
$jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()
$jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()
$jwtID = [guid]::NewGuid().Guid
$Cert = Get-ChildItem -Path cert:\Currentuser\My | Where {$_.Subject -eq "CN=AADApplicationWithCert"}
$decJwtHeader = #{
alg="RS256";
typ="JWT";
x5t=[System.Convert]::ToBase64String($Cert.GetCertHash())
} | ConvertTo-Json -Compress
$decJwtPayLoad = #{
aud = $appEndPoint;
exp = $jwtEndTimeUnix;
iss = $appClientID;
jti = $jwtID;
nbf = $jwtStartTimeUnix;
sub = $appClientID
} | ConvertTo-Json -Compress
$encJwtHeaderBytes = [system.text.encoding]::UTF8.GetBytes($decJwtHeader)
$encHeader = [system.convert]::ToBase64String($encJwtHeaderBytes) -replace '\+','-' -replace '/','_' -replace '='
$encJwtPayLoadBytes = [system.text.encoding]::UTF8.GetBytes($decJwtPayLoad)
$encPayLoad = [system.convert]::ToBase64String($encJwtPayLoadBytes) -replace '\+','-' -replace '/','_' -replace '='
$jwtToken = $encHeader + '.' + $encPayLoad
$toSign = [system.text.encoding]::UTF8.GetBytes($jwtToken)
$RSACryptoSP = [System.Security.Cryptography.RSACryptoServiceProvider]::new()
$HashAlgo = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new()
$sha256oid = [System.Security.Cryptography.CryptoConfig]::MapNameToOID("SHA256");
$RSACryptoSP.FromXmlString($Cert.PrivateKey.ToXmlString($true))
$hashBytes = $HashAlgo.ComputeHash($toSign)
$signedBytes = $RSACryptoSP.SignHash($hashBytes, $sha256oid)
$sig = [Convert]::ToBase64String($signedBytes) -replace '\+','-' -replace '/','_' -replace '='
$jwtToken = $jwtToken + '.' + $sig
$jwtToken
Related
I read a lot of questions here about this and i don't find what i search ...
I start scripting with powershell (just for information :p )
I want to modify a CSV file, exported from a database with information like "FirstName,LastName,OtherMail,IDAurion,Department". I have to add some titles to the HEADER, and i have to take informations in the CSV to concatenate some informations and put them in another CSV file.
I don't know if i explain well, i put you my script :
$OriginalCSV = "$PSScriptRoot\original.csv"
function Remove-StringLatinCharacters
{
PARAM ([string]$String)
[Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String))
}
Import-Csv -path $OriginalCSV -Encoding Default | ForEach-Object {
$OtherMail = $_.OtherMail
$IDAurion = $_.IDAurion
$Department = $_.Department
$FirstnameCSV = $_.FirstName
$FirstName = $FirstnameCSV -replace '(^\s+|\s+$)',''
$FirstnameNoLatin = Remove-StringLatinCharacters $FirstName
$FirstnameNoLatinNoSpace = $FirstnameNoLatin -replace '\s','-'
$FirstnameFirstLetterUpper = $Firstname.Substring(0,1).ToUpper()
$LastNameCSV = $_.LastName
$LastName = $LastnameCSV -replace '(^\s+|\s+$)',''
$LastnameNoLatin = Remove-StringLatinCharacters $LastName
$LastnameNoLatinNoSpace = $LastnameNoLatin -replace '\s',''
$LastnameFirstLetterUpper = $Lastname.Substring(0,1).ToUpper()
$UserPrincipalNameCSV = $FirstnameCSV + "." + $LastNameCSV + "#campus.ocellia.fr"
$UserPrincipalNameConcatene = $FirstnameNoLatinNoSpace + "." + $LastNameNoLatinNoSpace + "#email.fr"
$UserPrincipalName = $UserPrincipalNameConcatene.ToLower()
$MailNickName = $FirstnameNoLatinNoSpace.substring(0,1).toupper() + $($FirstnameNoLatin.substring(1).tolower() -replace '\s','') + $LastnameNoLatinNoSpace.toupper()
$Password = $FirstnameFirstLetterUpper + $LastnameFirstLetterUpper + $IDAurion + "$"
$Number = 0
Get-Content $OriginalCSV |
ForEach-Object{
If($Number -eq 0){
$_ + ",MailNickName" + ",Password" + ",UserPrincipalName"
$Number = 1
}
Else {
$_ + "," + $MailNickName + "," + $Password + "," + $UserPrincipalName
}
} | Out-File $PSScriptRoot\Modified-CSV.csv
}
This work for 1 line, but not for multiples line ...
Edit :
This is the examples for input CSV and how i want the resulting output CSV:
Input csv :
Firstname,Lastname,OtherMail,ID,Department
Pierre,DUPONT,pierre.dupont#mail.com,123456,Paris
Marie,CHANTAL,marie.chantal#mail.com,456789,Marseille
Output csv:
Firstname,Lastname,OtherMail,ID,Department,MailNickName,Password,UserPrincipalName
Pierre,DUPONT,pierre.dupont#mail.com,123456,Paris,PierreDupont,PD123456,p.dupont#entreprise.com
Marie,CHANTAL,marie.chantal#mail.com,456789,Marseille,MarieCHANTAL,MC456789,m.chantal#entreprise.com
I'm guessing this is what you're trying to do but since we don't have a minimal reproducible example it's quite hard to tell. I also attempted to improve your code, there was a lot of redundancy.
$OriginalCSV = "$PSScriptRoot\original.csv"
$ModifiedCSV = "$PSScriptRoot\modified.csv"
$txtInfo = (Get-Culture).TextInfo
function Remove-StringLatinCharacters
{
PARAM ([string]$String)
[Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String))
}
(Import-Csv -Path $OriginalCSV -Encoding Default | ForEach-Object {
$OtherMail = $_.OtherMail.Trim()
$IDAurion = $_.IDAurion.Trim()
$Department = $_.Department.Trim()
$FirstnameCSV = $_.FirstName.Trim()
$LastNameCSV = $_.LastName.Trim()
$firstName = (Remove-StringLatinCharacters $FirstName) -replace '\s','-'
$lastName = (Remove-StringLatinCharacters $LastName) -replace '\s'
$UserPrincipalName = "$FirstnameCSV.$LastNameCSV#campus.ocellia.fr"
$UserPrincipalNameConcat = "$firstName.$lastName#email.fr".ToLower()
$MailNickName = $txtInfo.ToTitleCase($firstName) + $lastName.ToUpper()
$Password = "{0}{1}$IDAurion$" -f $firstName.ToUpper()[0],$lastName.ToUpper()[0]
[pscustomobject]#{
FirstNameCSV = $FirstnameCSV
LastNameCSV = $LastNameCSV
OtherMail = $OtherMail
IDAurion = $IDAurion
Department = $Department
MailNickName = $MailNickName
Password = $Password
UserPrincipalNameCAMPUS = $UserPrincipalName
UserPrincipalNameEMAILFR = $UserPrincipalNameConcat
}
} | ConvertTo-Csv -NoTypeInformation) -replace '"' |
Out-File $ModifiedCSV
I use rest api with powershell to get details from the Service Bus queue message. I am not sure when it happened, but now Content is in bytes ex: Content:{64, 6, 115, 116…}.
How can I convert it to the normal string with data?
function Get-SAStoken {
param (
$QueueName,
$Access_Policy_Name,
$Access_Policy_Key
)
$expireInSeconds = 300
[Reflection.Assembly]::LoadWithPartialName("System.Web")| out-null
$uri="my.servicebus.windows.net/$QueueName"
#Token expires now+300
$expires=([DateTimeOffset]::Now.ToUnixTimeSeconds())+ $expireInSeconds
$signatureString=[System.Web.HttpUtility]::UrlEncode($uri)+ "`n" + [string]$expires
$HMAC = New-Object System.Security.Cryptography.HMACSHA256
$HMAC.key = [Text.Encoding]::ASCII.GetBytes($Access_Policy_Key)
$signature = $HMAC.ComputeHash([Text.Encoding]::ASCII.GetBytes($signatureString))
$signature = [Convert]::ToBase64String($signature)
$sasToken = "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}" -f [System.Web.HttpUtility]::UrlEncode($uri),
[System.Web.HttpUtility]::UrlEncode($signature),
[System.Web.HttpUtility]::UrlEncode($expires),
[System.Web.HttpUtility]::UrlEncode($Access_Policy_Name)
return $sasToken
}
function Get-SBmessage {
param (
$SASToken,
$Queue
)
$queue = $Queue
$header = #{ Authorization = $SASToken }
$postService = Invoke-WebRequest -Uri "https://my.servicebus.windows.net/$queue/messages/head" `
-Headers $header `
-Method Post
return $postService
}
$Queue = "capacity-checker"
$SAStokenRunningTest = Get-SAStoken -QueueName $Queue -Access_Policy_Name "pipeline" -Access_Policy_Key "key-for-sb-queue"
$SBmessage = Get-SBmessage -SASToken $SAStokenRunningTest -Queue $Queue
$SBmessage
So my solution is
[byte[]]$bytes = $SBmessage.Content
$msContent = [System.Text.Encoding]::ASCII.GetString($bytes)
Thanks, #MathiasR.Jessen for the help
I need to be able to download a folder with its contents from a blob storage account using the Azure Storage REST API only.
I have created a function (New-StorageAccountAthorisationHeader) that creates the (authentication) header that I can download a single file, but I cannot find any reference on how I might go about downloading the whole folder.
If I pass the folder as the $blob parameter, I get a BlobNotFound error.
The URL of the said folder is: https://mystorageaccount.blob.core.windows.net/acontainer/somefolder. The contents of "somefolder" looks like:
Folder1
FolderA
FileA.txt
FolderB
FileB.txt
FileC.txt
New-StorageAccountAthorisationHeader:
function New-StorageAccountAuthorizationHeader
{
[cmdletbinding()]
param
(
[string]$StorageAccountName,
[string]$Container,
[string]$Blob,
[string]$accesskey ,
[string]$ResourceUri,
[string]$xmsversion = "2017-04-17"
)
$xmsdate = Get-Date
$xmsdate = $xmsdate.ToUniversalTime()
$xmsdate = $xmsdate.toString('r')
function GetRestApiParameters
{
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true)]
[string]$Uri
)
if($Uri.Contains("?"))
{
Write-Verbose "URI to extract REST parameters: $uri"
return ($Uri.Split("?")[1]).Split("&")
}
}
Write-Verbose "Generating string for signature encryption..."
$partUrl = "/$StorageAccountName/"
if($Container)
{
$partUrl = $partUrl + "$Container/"
}
if($Blob)
{
$parturl = $partUrl + "$Blob"
}
######Don't change the line count or indentation of the here-string#####
$hereString = #"
GET
x-ms-date:$xmsdate
x-ms-version:$xmsversion
$partUrl
"#
$hereString =$hereString -replace "$([char]13)$([char]10)","$([char]10)" #Change `r`n to just `n
$empty = $oSignature = New-Object System.Text.StringBuilder
$empty = $oSignature.Append($hereString)
Write-Verbose "Appending parameters from URI into authorisation string..."
$restParameters = GetRestApiParameters -Uri $ResourceUri -Verbose
if ($restParameters -ne $null)
{
foreach ($param in $restParameters)
{
$empty = $oSignature.Append("$([char]10)$($param.Replace('=',':'))")
}
}
#$oSignature.toString()
Write-Verbose "Encrypting string..."
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Convert]::FromBase64String($accesskey)
$signature = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($oSignature.ToString()))
$signature = [Convert]::ToBase64String($signature)
Write-Verbose "Building header..."
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("x-ms-version", $xmsversion)
$headers.Add("x-ms-date", $xmsdate)
$headers.Add("Authorization", "SharedKey " + $StorageAccountName + ":" + $signature)
#$headers.Add("x-ms-blob-type","BlockBlob")
#$headers.Add("Content-Type", "application\xml")
Write-Verbose ("Header: $($headers | Out-String)")
Return $headers
}
And I would call it:
$StorageAccountName = "mystorageaccount"
$container = "acontainer"
$blob = "somefile.txt"
$uriToDownloadBlobs = "https://" + $StorageAccountName + ".blob.core.windows.net/$container/$blob"
$header = $null
$header = New-StorageAccountAuthorizationHeader -StorageAccountName $StorageAccountName -ResourceUri $uriToDownloadBlobs -Verbose -Container $container -Blob $blob
$result = Invoke-WebRequest -Headers $header -Uri $uriToDownloadBlobs -OutFile C:\Temp\$blob -PassThru
$result
So this works, but as I said, I'm after any hints to help with downloading the whole folder.
It looks like this is not possible? Although I'd be interested to see how it's done with the likes of Azure Storage Explorer.
My solution was to zip the files up and then use the above to download the single ZIP file. A few extra lines of code to compress and extract at either end, but it was the quickest way at the time and it works well with VSTS tasks.
I am trying to use a variable that loops through a list of words to be used in downloading password-protected files from a URL.
sites.txt:
BOS
HFD
LGA
NYC
PHI
WWD
Powershell Script:
$sites = Get-Content C:\Users\...\sites.txt
$time = (Get-Date).ToString("yyyyMMdd")
$Username = 'hello'
$Password = 'world'
$url = "http://my.website/" + $sites + "/some.csv"
$Path = "D:\...\...\" + $sites + "/some.csv"
$WebClient = New-Object System.Net.WebClient
$WebClient.Credentials = New-Object System.Net.Networkcredential($Username, $Password)
$WebClient.DownloadFile( $url, $path )
I am getting "unexpected token" errors so, I'm assuming that I am not using the $site variable correctly in the URL string.
$sites = Get-Content C:\Users\...\sites.txt
ForEach ($Site in $sites){
$url = "http://my.website/" + $Site + "/some.csv"
$Path = "D:\...\...\" + $Site + "\some.csv"
$Path
$url
}
This will pass your list into individual URL's
Edited to include change to '$Path' as well.
I'm trying to make PowerShell send a web request to our SSRS server and capture the results. I've hit a wall using the rs:FORMAT=EXCEL parameter in the SSRS url string. I have the following:
First, init the credentials:
$User = "MYDOMAIN\MyUser"
$PWord = ConvertTo-SecureString -String "WooHooStringP$W0rd" -AsPlainText -Force
$c = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
Now, request a report:
Invoke-WebRequest `
-UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) `
-Credential $c `
-Uri "http://myserver/ReportServer_DEV/Pages/ReportViewer.aspx?/folder+path/report+name"
This works fine. I can even grab the results (enclosing this request and using ().Content).
Then, specify a format instead of plain rendering:
Invoke-WebRequest `
-UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) `
-Credential $c `
-Uri "http://myserver/ReportServer_DEV/Pages/ReportViewer.aspx?/folder+path/report+name&rs:format=HTML4.0"
Note the rs:Format specification? Works like a charm.
Then, for the grande finale, give me an Excel file:
Invoke-WebRequest `
-UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) `
-Credential $c `
-Uri "http://myserver/ReportServer_DEV/Pages/ReportViewer.aspx?/folder+path/report+name&rs:format=EXCEL"
No can do, bud:
Invoke-WebRequest : The remote server returned an error: (401) Unauthorized.
At line:1 char:11
+ $bytez = (Invoke-WebRequest `
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Why does the rs:format=EXCEL option throw an Unauthorised exception where all the other URLs are served by SSRS?
I've figured it out! I went about this the wrong way: SSRS offers access through a webservice that PowerShell can consume without the need to hack the URL and capture a response. I found a script that did this and modified it to suit my purpose:
function GetRSConnection($server, $instance)
{
# Create a proxy to the SSRS server and give it the namespace of 'RS' to use for
# instantiating objects later. This class will also be used to create a report
# object.
$User = "DOMAIN\Username"
$PWord = ConvertTo-SecureString -String "Pa$$w0rd" -AsPlainText -Force
$c = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
$reportServerURI = "http://" + $server + "/" + $instance + "/ReportExecution2005.asmx?WSDL"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -Credential $c
$RS.Url = $reportServerURI
return $RS
}
function GetReport($RS, $reportPath)
{
# Next we need to load the report. Since Powershell cannot pass a null string
# (it instead just passses ""), we have to use GetMethod / Invoke to call the
# function that returns the report object. This will load the report in the
# report server object, as well as create a report object that can be used to
# discover information about the report. It's not used in this code, but it can
# be used to discover information about what parameters are needed to execute
# the report.
$reportPath = "/" + $reportPath
$Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
# initialise empty parameter holder
$parameters = #()
$RS.SetExecutionParameters($parameters, "nl-nl") > $null
return $report
}
function AddParameter($params, $name, $val)
{
$par = New-Object RS.ParameterValue
$par.Name = $name
$par.Value = $val
$params += $par
return ,$params
}
function GetReportInFormat($RS, $report, $params, $outputpath, $format)
{
# Set up some variables to hold referenced results from Render
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
# Report parameters are handled by creating an array of ParameterValue objects.
# Add the parameter array to the service. Note that this returns some
# information about the report that is about to be executed.
# $RS.SetExecutionParameters($parameters, "en-us") > $null
$RS.SetExecutionParameters($params, "nl-nl") > $null
# Render the report to a byte array. The first argument is the report format.
# The formats I've tested are: PDF, XML, CSV, WORD (.doc), EXCEL (.xls),
# IMAGE (.tif), MHTML (.mhtml).
$RenderOutput = $RS.Render($format,
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
# Determine file name
$parts = $report.ReportPath.Split("/")
$filename = $parts[-1] + "."
switch($format)
{
"EXCEL" { $filename = $filename + "xls" }
"WORD" { $filename = $filename + "doc" }
"IMAGE" { $filename = $filename + "tif" }
default { $filename = $filename + $format }
}
if($outputpath.EndsWith("\\"))
{
$filename = $outputpath + $filename
} else
{
$filename = $outputpath + "\" + $filename
}
$filename
# Convert array bytes to file and write
$Stream = New-Object System.IO.FileStream($filename), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
}
$RS = GetRSConnection -server "DEVBOX" -instance "ReportServer_DEV"
$report = GetReport -RS $RS -reportPath "folder name/report name"
$params = #()
$params = AddParameter -params $params -name "Month" -val "201311"
GetReportInformat -RS $RS -report $report -params $params -outputpath "i:\test" -format "EXCEL"
Using web request:
[string]$Domain = "DomainUsername"
[string]$Username = "Username"
[string]$Password = "Password"
[string]$ReportServer = "http://ssrsreportserver/ReportServer/ReportExecution2005.asmx" #Report Server
[string]$ReportLocation = "/Report Location/Report Name" #Report Location ON SSRS
$ReportLocation = $ReportLocation.Replace("/", "%2f")
$ReportLocation = $ReportLocation.Replace(" ", "+")
[string]$outputFile = $PSScriptRoot + '\Report.xlsx' #Save location for the file
#If the report has any parameters
[string]$ParamString = "";
$ParamString += "¶m1=paramvalue"
$ParamString += "¶m2=paramvalue"
[string]$URL = $ReportServer + "?" + $ReportLocation + "&rs:Command=Render&rs:Format=" + "EXCELOPENXML" + "&rs:ParameterLanguage=en-GB" + $ParamString
Write-Host $URL
$Req = [System.Net.WebRequest]::Create($URL);
$Req.Credentials = new-object System.Net.NetworkCredential($Username, $Password, $Domain)
$Req.Timeout = 30000;
$WebStream = $Req.GetResponse().GetResponseStream();
$MemStream = New-Object System.IO.MemoryStream
$WebStream.CopyTo($MemStream);
[long]$Len = $MemStream.Length;
[byte[]]$outBytes = [System.Byte[]]::CreateInstance([System.Byte], $Len)
$MemStream.Seek(0, [System.IO.SeekOrigin]::Begin);
$MemStream.Read($outBytes, 0, [int]$Len);
$WebStream.Close();
$MemStream.Close();
$MemStream.Dispose();
$Stream = New-Object System.IO.FileStream($outputFile), Create, Write
$Stream.Write($outBytes, 0, $outBytes.Length)
$Stream.Close()
Invoke-Item $outputFile