PowerShell is an efficient way to perform management tasks for Office 365, and also allows a great deal of automation through the use of PowerShell scripts to perform routine and repetitive tasks.
One of the challenges when using PowerShell for automation is handling authentication for the connection to various Office 365 services. Running a script as a scheduled task means that it needs to be able to use a set of stored credentials to authenticate to Office 365. Storing passwords in scripts is obviously not a good idea, because the passwords could easily be exposed if the script is obtained by another person who uses the same computer, or by accidentally sharing it online (such as on GitHub) with the creds still included.
Another challenge is constantly need to access the passwords to login. I keep my Office 365 admin credentials in a password database, and hopefully you use one as well (LastPass, 1Password, KeePass, Dashlane, etc). This makes it easy to have multiple credentials (such as admin accounts for different tenants, or for different tiers of administrative privilege) that each have unique, complex passwords. Thanks to web browser plugins for the likes of LastPass and 1Password it’s easy to login to Office 365 web-based portals, but copying and pasting passwords into PowerShell authentication prompts gets quite tedious.
I solve both of those problems by storing credentials on my admin workstation. This requires no special tools; it can achieved by using built-in PowerShell functionality. If the idea of storing credentials on your computer sounds like a security risk, please read on.
First, let’s look at how you might typically connect to Office 365 using PowerShell. The Connect-MsolService cmdlet will connect you to an Office 365 for management tasks such as assigning a license to a user.
PS C:\Scripts>Connect-MsolService
The cmdlet will prompt you for credentials to use for authenticating the session.
To avoid that credential prompt for repeat connections, you can use Get-Credential to capture your username and password as a credential object in PowerShell first, and use that for subsequent commands. For example:
PS C:\Scripts> $credential = Get-Credential cmdlet Get-Credential at command pipeline position 1 Supply values for the following parameters: Credential PS C:\Scripts> Connect-MsolService -Credential $credential
That solves part of the issue (repeatedly entering credentials), but doesn’t solve the issue of managing multiple accounts (should you create several credential objects every time you open a PowerShell console?) or running scheduled tasks (since you won’t be there to answer the credential prompt for the script).
But what we can do is store the password from the credential to a file on our computer. The password value is stored in memory as a secure string, and can be encrypted and stored in a text file, for example:
PS C:\Scripts> $credential.Password | ConvertFrom-SecureString | Out-File C:\Scripts\Keys\demoaccount.txt
The text file contains the encrypted value, which will look something like this.
Now, at this point you may be concerned that anyone who has access to that file could decrypt the password. Fortunately, that’s not the case. The ConvertFrom-SecureString cmdlet encrypts the password using the Windows Data Protection API. In short, this means that only the user who encrypted the password can decrypt it again, and they can only do so on the same computer that it was encrypted on. If the text file is accessed by another person, or by the same user on a different machine, they’ll be unable to decrypt it.
PS C:\Scripts> $securestring = Get-Content C:\Scripts\Keys\demoaccount.txt | ConvertTo-SecureString ConvertTo-SecureString : Key not valid for use in specified state.
Of course, you should still protect the files from being accessed by others, in much the same way you’d protect your SSH keys or your password database. For example, store them in a location on the workstation that is not included in cloud storage or backups, and is on a Bitlocker encrypted disk.
The downside of this approach is that you won’t be able to simply copy your stored credentials to another computer. Instead, you’ll need to recreate them on the new computer, which is not a huge hassle since you only need to do it once. There is another approach that avoids this problem, but it has other caveats. I’ll go into that in a separate article some other time.
So now that you can see how passwords can be securely stored and retrieved, how can this technique be used to manage stored credentials for day to day use, as well as for scheduled tasks? I’ve written some PowerShell functions that make it easy.
Preparation Tasks
The two functions that I’ve written use a variable named $KeyPath, and I recommend you add that variable to your PowerShell profile. If you don’t, the functions will prompt you for a path to save or retrieve credentials from, and that’s just going to get annoying.
Download the script containing the two functions, and run it to load the functions in your PowerShell console. I recommend adding them both to your PowerShell profile as well so that you can re-use them in any PowerShell session.
PS C:\Scripts> . .\Functions-PSStoredCredentials.ps1
Storing a New Credential
The first function will save a new stored credential. Simply run New-StoredCredential and enter the username and password when prompted.
The credential will be saves as a file named username.cred in the path that you configured for $KeyPath.
Using a Stored Credential
You can list the stored credentials in your $KeyPath folder by running Get-StoredCredential with the -List parameter.
PS C:\Scripts> Get-StoredCredential -List Username: admin@exchangeserverpro.onmicrosoft.com Username: admin@tenant.onmicrosoft.com
Get-StoredCredential can also be used to retrieve the stored credential and save it as a variable to then use with a cmdlet that requires authentication, for example:
PS C:\Scripts> $Credential = Get-StoredCredential -UserName admin@tenant.onmicrosoft.com PS C:\Scripts> Connect-MsolService -Credential $Credential
You can also use Get-StoredCredential in a one-liner like this.
PS C:\Scripts> Connect-MsolService -Credential (Get-StoredCredential -UserName admin@exchangeserverpro.onmicrosoft.com)
The difference between the examples above and the use of Get-MsolService earlier in this post, is that using the stored credential avoids the need to respond to an authentication prompt. So your passwords can be unique and complex without you needing to remember them or copy/paste them from a password manager.
And it’s not just for connecting to Office 365 services. You can use the stored credentials for pretty much any PowerShell cmdlet that uses credentials.
PS C:\Scripts> $credential = Get-StoredCredential -UserName administrator@exchangeserverpro.net PS C:\Scripts> Invoke-Command -ComputerName 10.1.4.3 -Credential $credential {Get-Service}
If you try to use a credential that doesn’t exist, you’ll simply get an error.
PS C:\Scripts> Connect-MsolService -Credential (Get-StoredCredential -UserName foo@exchangeserverpro.onmicrosoft.com) Unable to locate a credential for foo@exchangeserverpro.onmicrosoft.com
Using a Stored Credential for a Scheduled Task
The Get-StoredCredential function can also be easily integrated into scripts that you are running as scheduled tasks. The most important thing to remember is that the password can only be decrypted by the user account that encrypted it in the first place.
If your scheduled tasks run using a service account, you’ll need to log on as that service account to run New-StoredCredential, or use Runas to launch a PowerShell console as the service account. The stored credential file will also need to be located in a folder that the service account can access, and that location needs to be defined as $KeyPath in your script as well.
Download the New-StoredCredential and Get-StoredCredential Functions
The script to load the functions is available on GitHub. Comments and feedback are welcome in the comments below, or you can raise an issue on GitHub if you find a bug or have a suggested improvement to the script.
I need the PS script prompts for O365 authentication each time. How can this be handled/ bypassed ?
Any idea successfully automate the login to Microsoft call quality dashboard pop-up using PowerShell scrip
I don’t know if you still keep up with this old page, but wanted you to know that I have tried a dozen different things to get the scheduled task working when connecting to Office 365 and you provided the best explanation, as well as the easiest method to store the secured passwords. Thank you so much.
Paul
Anyway to make this work with a gMSA running the scheduled task? You mentioned the password is tied both to the user and the computer. Is there a way to tie it to the gMSA account. Maybe running the new-storedcredential under the context of the gMSA somehow?
Never mind, I was able to get it to work. I just wrote a one time script PS script with the password in plaintext and ran a scheduled task under the context of the gMSA account. Thanks for your work. This was a great help!
$keyPath = “D:\Scripts\PowerShell”
$User = “*******@onmicrosoft.com”
$PWord = ConvertTo-SecureString -String “******” -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
$Credential.Password | ConvertFrom-SecureString | Out-File “$($KeyPath)\$($Credential.Username).cred” -Force
Your option is to learn ADAL / MSAL and use AAD Conditional Access to avoid MFA prompt for the account&location combo (e.g SVC account, from corporate network = bypass MFA).
You don’t need to use ADAL/MSAL, you can just bypass MFA via AAD Conditional Access, but since you said Partner, I assume you will want to use your single credential to manage all of your customers’ tenants. That’s only doable with MSAL (maybe ADAL, but I’m not so sure).
Hi Paul, love the script, made my life easier while writing a reporting script. Everything works fine on my local PC, I have the Keypath in my profile file pointing to \\mydomain\netlogon for the .cred file.
The code is running inside VS Code at the moment on both boxes. The versions of PS are slightly different but both are V5 currently.
I’m now moving the script to a Server 2012 R2 box, and despite using the same code I’m getting this:
“ConvertTo-SecureString : Key not valid for use in specified state.”
I can use the List function without a problem, just can’t get the credential back out.
You need to re-create the credentials on the server, you cannot ‘re-use’ credentials on another PC, as mentioned in the post.
I got the scheduled task to work. I loaded the functions in my ISE profile, but forgot to load them in my powershell profile. Once I did that it worked. – duh
Thanks
Life saver! I was stuck with this same problem. Had the functions loaded into ISE profile but not powershell profile. Cheers!
I am having a very difficult time trying to use this when setting up a scheduled task. I cannot seem to get it to work. Outside of a scheduled task it works fine.
Can anyone outline the steps the have taken to make this work in a scheduled task?
Thanks
Usually you cannot bypass MFA if you use a MFA-enabled account for scripting. But, if you have a license, you can use Conditional Access to bypass MFA if all prerequisites are met. Or you create a non-MFA Service-Admin Account. With all its benefits and drawbacks.
Is there a way that this process can be used to connect to ExO via an MFA enabled account? What would need to be done to accomplish this?
Thank you
Would’ve also like to know.
Yes please help with mfa. Partners will require mfa as of 8/1/19 and may impact delegated PS sessions when connecting to EXO through delegated admin accounts.
Your option is to learn ADAL / MSAL and use AAD Conditional Access to avoid MFA prompt for the account&location combo (e.g SVC account, from corporate network = bypass MFA).
You don’t need to use ADAL/MSAL, you can just bypass MFA via AAD Conditional Access, but since you said Partner, I assume you will want to use your single credential to manage all of your customers’ tenants. That’s only doable with MSAL (maybe ADAL, but I’m not so sure).
Hi Paul,
Are you able to provide an example of using Get-StoredCredential when creating a scheduled task? I can’t work out how to use it with Registered-ScheduledTask. Do I use it with New-ScheduledTaskPrincipal?
Cheers
Pingback: Commvault Mailbox backup using the new pseudoclient architecture | Backup Bits
Hi Paul,
I am running Powershell scripts as Scheduled tasks on an in house on prem server against SharePoint online. One thing I run into intermittently is 401 unauthorized user issues against my SharePoint tenant. Even through the account I am using is an admin account and works fine I will get this error periodically. I’ve yet to find anything online that would explain in detail the authorization requirements of scripts needed to be run daily, weekly etc. Can you by chance point me to any documentation that outlines setting up PowerShell scripts to run on a regular schedule and what is required to ensure authorization errors don’t occur? I find these run like clock work for months and then start failing for no apparent reason, then all of a sudden are working fine again. I was going to put in a service ticket but saw your blog and thought I’d ask.
Thanks
Don
The Real Person!
The Real Person!
Nothing specific springs to mind, but a good place to start would be the log of sign-ins in the Azure AD portal, see what other information it might reveal about the failed logins.
Be aware that some functions that connect to Microsoft Online products clear the password in the stored credential. So if you are using a script which uses both Connect-AzureAD, Connect-MicrosoftTeams and Connect-SPOService you may have issues reusing the credential object. They posted that they had fixed this in AzureADPreview recently, but my test showed it was still not fixed.
As a workaround you can mark the password as read only after creating the credential object: $creds.Password.MakeReadOnly()
See: https://github.com/Azure/azure-docs-powershell-azuread/issues/169
Your functions are great.
I have modified my default profile to load the functions and the variable.
I wanted to write a custom script that would list the saved credentials via “Out-GridView -Passthru”.
Normally, it would give me a list in a windows and let me choose the file to automatically connect afterwards.
However the following command only display a list in the active PowerShell window :
Get-StoredCredential -List | Out-GridView
Is there a way to do what I want with your script as is?
Thank you & regards.
Alexandre
The Real Person!
The Real Person!
That function just uses Write-Host to output the stored creds to the console, so it’s not suitable for piping to Out-GridView. You’d need to rewrite that part of the function.
I have followed your recommendation and modified a bit your function.
I have added this to your code in the “param” section :
[Parameter(Mandatory=$false, ParameterSetName=”ConnectO365″)]
[string]$ConnectO365,
And I add this section :
if ($ConnectO365) {
$FilePath = “D:\Alex\Documents\WindowsPowerShell\Save-Cred”
$FileList = Get-ChildItem -path $FilePath -Name *.cred | Out-GridView -PassThru
$credential = Get-StoredCredential -UserName $FileList.substring(0,$FileList.length -5)
Connect-MsolService -Credential $Credential
}
When I start a new PS Session, the script is loaded and I can directly call the functions.
If I type “Get-StoredCredential -ConnectO365” then I get an error but I put whatever value afterward the window opens and I can choose my cred file. Then I click OK and my Office 365 connects flawlessly. Spendid.
You may have noticed that I am not a good scripter/coder.
So, I have one more question.
Here is my question. How do I do if I don’t want to put a value after the “-ConnectO365” parameter.
One more time, excellent job. I will be very useful for my daily job.
Thanks & regards.
Alexandre
It works fine
The Real Person!
The Real Person!
Try changing that parameter to a switch instead of a string.
[switch]$ConnectO365
/HI Paul
i am gettting New-StoredCredential : The term ‘New-StoredCredential’ is not recognized as the name of a cmdlet, after running the script file
Hello Paul thank you for your valuable work . I am still beginner in using Windows PowerShell with office 365 . The task that I have given is to share my file of windows powershell fully automated to be used with another person and the credentials should be secure “without writing the password by the guy he want only to run my code and get the requirement” .
Also he is an administrator at the same domain. Thank you
The Real Person!
The Real Person!
If you want the credentials to be securely stored then you can use my technique demonstrated above for storing the credentials. Any scripts or commands you want to run must then be written to support the use of the stored credentials.
I need the PS script prompts for O365 authentication each time. How can this be handled/ bypassed ?
Any idea successfully automate the login to Microsoft call quality dashboard pop-up using PowerShell scrip
Thanks for this very valuable script. I have a problem using it with full SAM account names. For example, I run some scripts under a service account using
{
$AdminUser = ‘Domain\PrivilegedUser’
$pwd1 = ‘PrivilegedUser password in plain text’
$pwd = ConvertTo-SecureString -string $pwd1 -AsPlainText -Force
$AdminCredential = New-Object System.Management.Automation.PSCredential $AdminUser, $pwd
Start-Process -FilePath powershell_ise.exe -Credential $AdminCredential
For some reason,
$AdminUser = ‘PrivilegedUser’
and
$AdminUser = ‘PrivilegedUser@domain’
Don’t work.
I can’t create credentials for ‘Domain\PrivilegedUser’ because the backslash gets inserted into the path for the stored credential.
and the following is not working.
Start-Process -FilePath posershell_ise.exe -Credential (Get-StoredCredential -UserName PrivilegedUser)
or
Start-Process -FilePath posershell_ise.exe -Credential (Get-StoredCredential -UserName PrivilegedUser@domain)
Is there a workaround? I was thinking something like
Start-Process -FilePath posershell_ise.exe -Credential “Domain\ + (Get-StoredCredential -UserName PrivilegedUser)”
The Real Person!
The Real Person!
Use a UPN instead of a domain\username?
Yeah, tried that and it doesn’t work. Seems to want domain\username and not UPN or username
The Real Person!
The Real Person!
You could retrieve the stored cred and then decrypt the password (https://blogs.technet.microsoft.com/heyscriptingguy/2013/03/26/decrypt-powershell-secure-string-password/) and use that in your command. You might need to hard-code the domain into the script and only store the username/password in the stored cred without the domain name.
That should work. Thank you.
Hi Paul,
great preparation for the follow up article to receive O365 Notifications via mail. However i have issues with the implementation of the required functions. The functions are not available after executing functions-psstoredcredentials.ps1.executed from either “normal” powershell, Azure Active Directory Shell, also AzADv2 Shell – and runas Administrator.
Do you have a hint / to what this might be related?
Any idea is appreciated since i want to get the O365 Message center via Mail.T
Thank you & best regards
Markus
You need to run the line below:
. .\Functions-PSStoredCredentials.ps1
Note the two dots.
Hi Paul,
I noticed you are using the older MSOnline PowerShell module in your examples. It may be useful to start using the newer Azure Active Directory PowerShell V2 module instead, as we will begin deprecating the MSOnline module when we have migrated the functionality of the MSOnline module to the newer module – currently planned for the Spring of 2017.
Thanks,
Rob de Jong
The Real Person!
The Real Person!
My function works with any cmdlet that has a -Credential switch, so it’s just an example.
I’ve been playing with the new AzAD PS V2 module. Spring is a long time away here in Australia. Or perhaps you mean Spring in the northern hemisphere. I can never tell with these season-based release timelines.