How to Fix PowerShell Script from Basic Auth to Modern Auth - powershell

I have a PowerShell script that, after pinging a server address, uses Basic Auth to send an automated email via Task Scheduler. Microsoft has deprecated Basic Auth in Exchange Online in favor of Modern Auth, but I do not see clear directions for updating a PowerShell script to use Modern Auth.
This is an example of the Basic Auth that I need to convert.
$secpasswd = ConvertTo-SecureString “password” -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential (“user#place.com”, $secpasswd)
Send-MailMessage -SmtpServer smtp.office365.com -Port 587 -From user#place.com -To otheruser#place.com -Subject test -Body test -Credential $mycreds -UseSsl"
Can someone point me to an example of Modern Auth being used in a similar script or share what I need to do to update and run the above script?
Many Thanks!

As per the resources in my original comment.
Send-MailMessage is obsolete and no longer supported. Microsoft says this cmdlet does not guarantee a secure connection to SMTP servers. As per:
https://aka.ms/SendMailMessage
Therefore use the below:
Send-MgUserMail - MS Docs
Note: Send-MgUserMail requires a more complex parameter structure.
$EmailMessageContent=#'
<Strong> This is a Test Message</Strong><br>
Modern auth testing
'#
$params = #{
Message = #{
Subject = "Using MSGraph"
Body = #{
ContentType = "html"
Content = $EmailMessageContent
}
ToRecipients = #(
#{
EmailAddress = #{
Address = "SomeRecipientEmaiAddress"
}
}
)
}
}
Import-Module Microsoft.Graph.Users.Actions
Connect-MgGraph -Scopes Mail.Read
Send-MgUserMail -UserId 'SomeSenderEmailAddress' -BodyParameter $params
Point of note:
SMTP AUTH will still be available when Basic authentication is permanently disabled on October 1, 2022. The reason SMTP will still be available is that many multi-function devices such as printers and scanners can't be updated to use modern authentication.
See also the below for full details of the why and so on... (and more sample code):
Authenticate an IMAP, POP or SMTP connection using OAuth
Moving on from Send-MailMessage: Sending Email from PowerShell using
the Graph API
The Send-MailMessage Conundrum
Largely because of history, Exchange Online supports a wide variety of
connectivity protocols. Microsoft is making some progress to convince
customers to disable basic authentication for protocols they never
use, and has upgraded older protocols like POP3 and IMAP4 to use OAuth
2.0 for modern authentication. As discussed in this blog, tenants will need to find PowerShell scripts which call the Send-MailMessage cmdlet
and eventually upgrade the code with a more modern method to send
email.
The Send-MailMessage cmdlet depends on the SMTP AUTH protocol to send
email using basic authentication. Microsoft announced OAuth 2.0
support for SMTP AUTH in April 2020, but this doesn’t mean that an
off-the-shelf replacement cmdlet is available. Microsoft says that the
announcement “is for interactive applications to enable OAuth for IMAP
and SMTP [AUTH].” In effect, this means mail clients or other
applications which send, read, or otherwise process email. A quick
trip to the referenced page leaves no doubt that this means more than
replacing a few lines of code in a PowerShell script.

Thanks #postanote for your suggestions, the fix eventually came down to this:
from user#place.com I had to drop the 'place.com' and just keep the alias
with -SmtpServer smtp.office365.com I had to drop 'office365.com' and replace with '[uni].edu'

Related

Authentication error when using SharePoint Migration Tool PowerShell cmdlets

Server 2012 R2 file share to SharePoint Online migration
I am attempting to automate scheduling some file share synchronization to SharePoint Online using the migration tool, however I get an error that my credentials are incorrect.
The same credentials work using the GUI version of the SPMT so I know they are correct, and these credentials are for the global administrator of 365 so there should absolutely be no permissions issues.
The error that I receive:
Task 7967a651-6a2a-47ed-afcd-6b1567496e7d did NOT pass the parameter validation, the error message is 'Username or password for target site https://tenant.sharepoint.com/sites/FileShareSite is not correct' Migration finished, but some tasks failed! You can find the report and log at X:\log.log
The code I am using:
Import-Module Microsoft.SharePoint.MigrationTool.PowerShell
$SPOUrl = "https://tenant.sharepoint.com/sites/FileShareSite"
$Username = "admin#tenant.onmicrosoft.com"
$Password = ConvertTo-SecureString -String "PasSWorD" -AsPlainText -Force
$SPOCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $Password
Register-SPMTMigration -SPOCredential $SPOCredential -Force -MigrateWithoutRootFolder -PreserveUserPermissionsForFileShare $true -WorkingFolder "X:\log"
Add-SPMTTask -FileShareSource "\\file-server\shares\ShareOne" -TargetSiteUrl $SPOUrl -TargetList "ShareOne" -TargetListRelativePath "/"
Start-SPMTMigration -NoShow
According to the logs, I am seeing 400 response codes, as well as some 'An existing connection was forcibly closed by the remote host.'
Something so simple so I don't know what the problem could be; OS is supported, credentials are correct, URL is correct, all these settings work in the GUI version of the tool.
In the logs I see references to logging into AAD, we do not have AAD on this tenant, I am a little curious to know if that is just semantics or if that is part of the problem. I would have assumed the GUI and the PowerShell module use the same mechanisms behind the scenes. Error happened in AAD login MSAL.Desktop.4.37.0.0.MsalServiceException: ErrorCode: user_realm_discovery_failed Microsoft.Identity.Client.MsalServiceException: Response status code does not indicate success: 400 (BadRequest).
So I figured it out, the issue turned out to be PowerShell using an outdated SSL/TLS cipher. I forced TLS1.2 on the PowerShell session using [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 and it is now working as expected.

Execute an App registration without AzureAD

For a professional project, a chunk of the pipeline must be able to create an application (the first App registration, so I only have a global Admin) automatically within Azure AD. So far I used AzureAD which works well with Powershell 5.6 on Windows.
I now must be able to run the code with Ubuntu 20.04 and its Powershell 7.2. Unfortunately for me, AzureAD module is only supported on non-core Windows PowerShell, therefore it does not work on core PS6 or PS7. A very simplified piece of code is the following:
# Connection infos
$tenantId = "abcdef12345-1234-1234-124-abcdef12346789"
$account = "my_admin#domain.com" # Is cloud Admin by default
$password = ConvertTo-SecureString "MyPassword" -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential -ArgumentList ($account, $password)
Connect-AzureAD -Credential $psCred -Tenant $tenantId
# Create app
$appName = "MyApp"
New-App -appName $appName -tenant_id $tenantId
I am stuck and my question is the following: how could I run such an operation with Powershell 7.2 considering AzureAD is not usable? I did check Connect-MgGraph for the connection part only (https://github.com/microsoftgraph/msgraph-sdk-powershell) but the clientId is an infos that I don't have -and want to create-.
Thanks in advance
You can use DeviceLogin as explained in this article to obtain an oAuth access token for you Global Administrator account in PowerShell (independent of the version) but this first step needs a human interaction.
After obtaining the token, you can use it to make Graph API calls with your Global Administrator permissions to create an application.
Once you create your first application, you must attribute required permissions and use it to automate the process (obtain token programmatically using API calls) for application creation in PowerShell.
You could use Resource Owner Password Credentials (ROPC) to authenticate, however Microsoft actively discourages it in their documentation due to the security implications of sending a password over the wire.
If the security issues present with this method of authentication are still tolerated within your acceptance criteria, you would still need a ClientID. Luckily, AzureAD has a well-known ClientID that you can use to authenticate. This ID is 1950a258-227b-4e31-a9cf-717495945fc2
The below Powershell code should get you started. I've basically translated the HTTP request within Microsoft's documentation into a splatted Invoke-RestMethod command.
$LoginWithROPCParameters = #{
URI = "https://login.microsoftonline.com/contoso.onmicrosoft.com/oauth2/v2.0/token"
Method = "POST"
Body = #{
client_id = "1950a258-227b-4e31-a9cf-717495945fc2"
scope = "user.read openid profile offline_access"
username = "username#contoso.onmicrosoft.com"
password = "hunter2"
grant_type = "password"
}
}
Invoke-RestMethod #LoginWithROPCParameters

What is the best way to store account credentials (especially password) for an automated email script?

I am writing a simple script (windows powershell) to send automated emails. A computer will be running at all times collecting data and then sending emails at regular intervals. Part of sending an email obviously is collecting credentials, and since its automated we cant have somebody there everytime to enter the user and password. The obvious solution is to just store info in $user and $pass vars in the script but this seems horribly unsafe to me, and prone to attacks. Is there any better way to do this? Maybe setup a secure email connection once with the user address and not have to enter it in again? I am new to powershell so im not really clear on the best ways to do this. Currently what I am doing is:
$from = 'UserEmail#anotherEmail.com'
$to = 'someEmail#SomeMail.com'
$smtpServer = 'smtp-mail.outlook.com'
$smtpPort = '587'
$mailSubject = 'PowerShell Script Email Test'
$password = 'p#ssword'
$mailBody = 'body text'
$credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $from, $($password | ConvertTo-SecureString -AsPlainText -Force)
Send-MailMessage -To "$to" -From "$from" -Subject $mailSubject -SmtpServer $smtpServer -UseSsl -Credential $credentials -BodyAsHtml -Body $mailBody
Any advice or documentation to read would be much appreciated
You may want to investigate the Protect-CMSMessage cmdlet, which allows you to encrypt/decrypt data using public key cryptography so that only users with the correct certificate would be able to decrypt the password.
If that seems like overkill, another, easier but possibly less secure, option is to export the credentials to XML and read them back when required.
To create the file, do this:
Log on as the user the script will be running as
Execute this command: Get-Credential | Export-CliXml <path>\cred.xml
When prompted enter the username/password to be used in the script
The resulting XML file will have the username and password securely stored and can be read back like this:
$cred = Import-CliXml <path>\cred.xml
You can then pass $cred to any cmdlet that has a -Credential parameter.
The password is encrypted in such a way that it can only be opened by the same user on the same computer, so if someone else opens it they won't be able to access the details. Obviously, if they can log on as the user who encrypted it (or convince that user to run a 'bad' script), then they will have access to the details, but otherwise this is pretty secure.
A third option is to use the built-in Credential Manager in Windows. This needs some complicated .NET interop for older systems, but luckily some nice person has already done the hard work for you:
PowerShell Credentials Manager
This is a bit easier in Windows 10:
PasswordVault Class

Deleting Gmail Emails via Google API using Powershell v2.0

$user = "example#gmail.com"
$pass= "examplepassword"
$secpasswd = ConvertTo-SecureString $user -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ($pass, $secpasswd)
Invoke-RestMethod 'https://www.googleapis.com/gmail/v1/users/me/messages/0' -Method Delete -Credentials $cred
So, my problem here is twofold.
I originally tried using Invoke-WebRequest to delete gmail emails via the Google API with a http delete request. However, this did not work because Powershell 2.0 does not support Invoke-WebRequest.
Thereafter, I turned to attempting to utilize Invoke-RestMethod after experimentation with IMAP and POP3, which both required external dependencies (Adding .dlls to the machines I am working with is not optimal).
Therefore, if someone could show me the appropriate way to delete an email via the Google API in Powershell, I would appreciate it. I have provided some sample code as to what I am working with above. Please excuse any mistakes it may contain, as I am relatively new to Powershell, and my experience remains limited in working with RESTful services.
The GMail API is going to require Oauth2 authentication unless this is a gsuit / domain admin / GMail account in which case you can use a service account for authentication. In either case you cant use login and password.
My powershell knowledge is very limited have you considered doing this directly though the mail server IMAP and SMTP and not using the API. No idea if that's possible or not with powershell
Update:
I was able to do it using Invoke-WebRequest you will still need to get an access token first.
Invoke-WebRequest -Uri "https://www.googleapis.com/gmail/v1/users/me/messages/0?access_token=$accesstoken"-Method Get | ConvertFrom-Json
seams to also work
Invoke-RestMethod -Uri "https://www.googleapis.com/gmail/v1/users/me/messages/0?access_token=$accesstoken"-Method Get
Put up a the code for OAuth on GitHub if your interested: Google Oauth Powershell

Send mail from powershell as anonymous user

I've always used the cmdlet Send-MailMessage without specifying any -Credential. Now I need to send mail using the anonymous user. The workaround I've found is this piece of code
$pass = ConvertTo-SecureString "anyString"-AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "NT AUTHORITY\ANONYMOUS LOGON",$pass
Send-MailMessage -Credential $cred #...
It works, but is this the correct method to get the anonymous is and send anonymous mail?
You can send mail from any address, it just depends on whether the receiving mail server cares about whether it can verify the sending user or not.
For example if you send from anon#microsoft.com and microsoft does not have that account, OR they have SPF records to indicate whether the sending mail server is valid or not, then the receiving mail server might(not always) reject it.
Just make sure your email user actually exists and has a valid domain... and you can send from anonymous# or noreply# or whatever you want to.
Okay, from the comments we know we're dealing with an Exchange server. Whether or not it will do an anonymous relay depends on the configuration of the Recieve Connectors. But those restrictions only apply to the network connections. If you run Send-MailMessage on the Exchange server and use 'LocalHost' as your SMTPServer it didn't go through a receive connector so those restrictions don't apply.
If you have remoting enabled on the Exchange server you can use that to do a local invocation to send email without having to modify the Receive Connector configurations:
$EmailParams =
#{
To = '<Email To>'
From = '<Email From>'
Subject = '<Email Subject>'
Body = '<Email Body>'
SMTPServer = 'localhost'
}
$Scriptblock = [Scriptblock]::Create(
"Send-MailMessage $(&{$args} #EmailParams) ")
Invoke-Command -ScriptBlock $Scriptblock -ComputerName ExchangeServer