Changes in System.Net.HttpWebRequest between Powershell versions? - powershell

I am having some trouble converting a Powershell script from PS5 to PS7.
My goal is to get information on a remote server cert.
This IP can be anything with a cert:
$url = "https://192.168.1.1"
[Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$req = [Net.HttpWebRequest]::Create($url)
$req.GetResponse()
[datetime]$expiration =
[System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString())
Write-Output $expiration
When run in Powershell 5, this script completes successfully with the output below:
IsMutuallyAuthenticated : False
Cookies : {}
Headers : {Accept-Ranges, Content-Length, Content-Type, Date...}
SupportsHeaders : True
ContentLength : 98540
ContentEncoding :
ContentType : text/html
CharacterSet : ISO-8859-1
Server : Microsoft-IIS/10.0
...
Date : 7/7/2020 12:00:00 AM
...
This script errors on $req.GetResponse when run in Powershell 7.
MethodInvocationException: Exception calling "GetResponse" with "0" argument(s): "The SSL connection could not be established, see inner exception. There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspace type. The script block you attempted to invoke was: $true "
Any ideas?

Related

EWS and AutoDiscoverURL error using Azure AD Certificate with Powershell

I've tried with and without Secret ID, and now with a self-signed Certificate and I keep getting the same error:
Exception calling "AutodiscoverUrl" with "2" argument(s): "The
expected XML node type was XmlDeclaration, but the actual type is
Element."
My PowerShell script:
$TenantId = "blahblah"
$AppClientId="blahblah"
$EDIcertThumbPrint = "blahblah"
$EDIcert = get-childitem Cert:\CurrentUser\My\$EDIcertThumbPrint
$MsalParams = #{
ClientId = $AppClientId
TenantId = $TenantId
ClientCertificate = $EDIcert
Scopes = "https://outlook.office.com/.default"
}
$MsalResponse = Get-MsalToken #MsalParams
$EWSAccessToken = $MsalResponse.AccessToken
Import-Module 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll'
#Provide the mailbox id (email address) to connect via AutoDiscover
$MailboxName ="email#myemaildomain.com.au"
$ews = [Microsoft.Exchange.WebServices.Data.ExchangeService]::new()
$ews.Credentials = [Microsoft.Exchange.WebServices.Data.OAuthCredentials]$EWSAccessToken
$ews.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
$ews.AutodiscoverUrl($MailboxName,{$true})
I've searched that error message everywhere, and I am not getting anywhere. The error doesn't make sense, because I am not referring to XML in any way - unless it's embedded inside the EWS?
The only time this works is when I don't use either a Secret ID nor a Certificate, but the Token only lasts 1 hour! I need to make this automatic, so I can get into my mailbox and extract files from emails.
Thanks
UPDATE
So I've removed the AutoDiscoverUrl() and I now getting another error:
Exception calling "FindItems" with "2" argument(s): "The request
failed. The remote server returned an error: (403) Forbidden."
Trace log:
The token contains not enough scope to make this call.";error_category="invalid_grant"
But why when I have an Oauth token!?
My code in trying to open the "Inbox":
$results = $ews.FindItems(
"Inbox",
( New-Object Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList 100 )
)
$MailItems = $results.Items | where hasattachments
AutoDiscoverv1 doesn't support the client credentials flow so you need to remove the line
$ews.AutodiscoverUrl($MailboxName,{$true})
It's redundant anyway because your already setting the EWS endpoint eg
$ews.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
The only time that endpoint would change is if you had mailbox OnPrem in a hybrid environment and there are other ways you can go about detecting that such as autodiscoverv2.

Powershell FTP won't list files on server

I want to make a backup script for ZyWall USG 20.
I used this Powershell code to get a list of the content in the directory:
[System.Net.FtpWebRequest]$ftp = [System.Net.WebRequest]::Create("ftp://*server*/*path*")
$ftp.Credentials = New-Object System.Net.NetworkCredential("*user*","*password*")
$ftp.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory
$response = $ftp.getresponse()
echo $response
I expected to get a list of all the files..instead I get this:
PS Z:\> ...\list-files-test.ps1
ContentLength : -1
Headers : {}
SupportsHeaders : True
ResponseUri : ftp://*server*/*path*/
StatusCode : OpeningData
StatusDescription : 150 Opening BINARY mode data connection for file list
LastModified : 01.01.0001 00:00:00
BannerMessage : 220 FTP Server (ZyWALL USG 20) [::ffff:*server*]
WelcomeMessage : 230 User *user* logged in
ExitMessage :
IsFromCache : False
IsMutuallyAuthenticated : False
ContentType :
PS Z:\>
Why don' I get a list of files?
I checked it with filezilla - the folder is not empty.
And I can download specific files using [System.Net.WebRequestMethods+Ftp]::DownloadFile.
WebRequest.GetResponse() makes the remote call and gets the response details, but there's no content within the response itself.
Instead, you need to continue on and read the response stream out of the response with WebResponse.GetResponseStream().

Creating proxy with bypass list fails if it contains "*"

The code:
New-Object System.Net.WebProxy($Env:http_proxy, $true, #('localhost', '*.domain.com')
fails with the error:
New-Object : Exception calling ".ctor" with "3" argument(s): "parsing "*.domain.com" - Quantifier {x,y} following nothing."
At line:1 char:6
+ $p = New-Object System.Net.WebProxy($Env:http_proxy, $true, #('*.domain.com', 'l ...
The Quantifier {x,y} following nothing is regex error which is strange. I tried to use regex escape chars but nothing.
Any solutions ?
I messed that up at least twice - the following however seems to add it correctly though one at a time:
$wp = New-Object System.Net.WebProxy($Env:http_proxy, $true)
$wp.BypassArrayList.Add('localhost')
$wp.BypassArrayList.Add('*.domain.com')
Output
Address :
BypassProxyOnLocal : True
BypassList : {localhost, *.domain.com}
Credentials :
UseDefaultCredentials : False
BypassArrayList : {localhost, *.domain.com}
Looking at this it's stated the third parameter is an array of regex strings - *.domain.com isn't a valid regex as a character class must precede the *.
It works if you change it to .*.domain.com however:
[PS] > New-Object System.Net.WebProxy($Env:http_proxy, $true, #("localhost.domain.com",".*.domain.com"))
Address :
BypassProxyOnLocal : True
BypassList : {localhost.domain.com, .*.domain.com}
Credentials :
UseDefaultCredentials : False
BypassArrayList : {localhost.domain.com, .*.domain.com}

Get Domain from URL in PowerShell

I'm looking to strip out the domain in this scenario using PowerShell. What is the most effective method to get example.com out of the following variable?
$URL = "http://www.example.com/folder/"
(some sort of regex command here to convert/strip $URL into $DOMAIN using PowerShell)
$DOMAIN = "example.com" #<-- taken from $URL
I've searched and I've found results for finding the IP address from a domain but I need to establish what the domain is first using regex (or another method).
Try the URI class:
PS> [System.Uri]"http://www.example.com/folder/"
AbsolutePath : /folder/
AbsoluteUri : http://www.example.com/folder/
LocalPath : /folder/
Authority : www.example.com
HostNameType : Dns
IsDefaultPort : True
IsFile : False
IsLoopback : False
PathAndQuery : /folder/
Segments : {/, folder/}
IsUnc : False
Host : www.example.com
Port : 80
Query :
Fragment :
Scheme : http
OriginalString : http://www.example.com/folder/
DnsSafeHost : www.example.com
IsAbsoluteUri : True
UserEscaped : False
UserInfo :
And remove the www prefix:
PS> ([System.Uri]"http://www.example.com/folder/").Host -replace '^www\.'
example.com
Like this:
PS C:\ps> [uri]$URL = "http://www.example.com/folder/"
PS C:\ps> $domain = $url.Authority -replace '^www\.'
PS C:\ps> $domain
example.com
For properly calculating the sub-domain, the trick is you need to know the second to last period. Then you take a substring of that second to last period (or none if it is only one .) to the final position by subtracting the location of second period (or 0) from total length of domain. This will return the proper domain-only and will work regardless of how many sub-domains are nested under the TLD:
$domain.substring((($domain.substring(0,$domain.lastindexof("."))).lastindexof(".")+1),$domain.length-(($domain.substring(0,$domain.lastindexof("."))).lastindexof(".")+1))
Also note that System URI itself is valid 99% of the time but I'm parsing my IIS logs and finding that with VERY long (often invalid/malicious requests) URIs it does not properly parse and fails those.
I have this in function form as:
Function Get-DomainFromURL {
<#
.SYNOPSIS
Takes string URL and returns domain only
.DESCRIPTION
Takes string URL and returns domain only
.PARAMETER URL
URL to parse for domain
.NOTES
Author: Dane Kantner 9/16/2016
#>
[CmdletBinding()]
param(
[Alias("URI")][parameter(Mandatory=$True,ValueFromPipeline=$True)][string] $URL
)
try { $URL=([System.URI]$URL).host }
catch { write-error "Error parsing URL"}
return $URL.substring((($URL.substring(0,$URL.lastindexof("."))).lastindexof(".")+1),$URL.length-(($URL.substring(0,$URL.lastindexof("."))).lastindexof(".")+1))
}

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