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.

new-stored-credential-popup

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.

powershell-stored-credential

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.

new-stored-credential-popup-2

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.

About the Author

Paul Cunningham

Paul is a former Microsoft MVP for Office Apps and Services. He works as a consultant, writer, and trainer specializing in Office 365 and Exchange Server. Paul no longer writes for Practical365.com.

Comments

  1. babulal sah

    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

  2. Deb Codding

    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.

  3. VHA

    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?

    1. VHA

      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

  4. Jeremy Bradshaw

    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).

  5. John

    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.

    1. Simon

      You need to re-create the credentials on the server, you cannot ‘re-use’ credentials on another PC, as mentioned in the post.

  6. Damon Villar

    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

    1. Jon

      Life saver! I was stuck with this same problem. Had the functions loaded into ISE profile but not powershell profile. Cheers!

  7. Damon Villar

    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

  8. Richard

    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.

  9. Damon S Villar

    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

    1. Markus Swanepoel

      Would’ve also like to know.

      1. Jeff

        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.

        1. Jeremy Bradshaw

          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).

  10. Malcolm Swan

    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

  11. Don Landry

    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

    1. Avatar photo
    2. Jeremy Hagan

      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

  12. Alexandre

    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

    1. Avatar photo
      1. Alexandre

        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

      2. Sumanth S

        /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

  13. Ahmad yamout

    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” .

    1. Ahmad yamout

      Also he is an administrator at the same domain. Thank you

    2. Avatar photo

      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.

      1. babulal sah

        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

  14. Art Alexion

    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)”

      1. Art Alexion

        Yeah, tried that and it doesn’t work. Seems to want domain\username and not UPN or username

        1. Avatar photo
          1. Art Alexion

            That should work. Thank you.

  15. Markus

    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

    1. Ryan

      You need to run the line below:
      . .\Functions-PSStoredCredentials.ps1

      Note the two dots.

  16. Rob de Jong

    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

    1. Avatar photo

      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.

Leave a Reply