start-m365DomainPasswordExpirationManagement.ps1

#############################################################################################
# DISCLAIMER: #
# #
# THE SAMPLE SCRIPTS ARE NOT SUPPORTED UNDER ANY MICROSOFT STANDARD SUPPORT #
# PROGRAM OR SERVICE. THE SAMPLE SCRIPTS ARE PROVIDED AS IS WITHOUT WARRANTY #
# OF ANY KIND. MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING, WITHOUT #
# LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR #
# PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLE SCRIPTS #
# AND DOCUMENTATION REMAINS WITH YOU. IN NO EVENT SHALL MICROSOFT, ITS AUTHORS, OR #
# ANYONE ELSE INVOLVED IN THE CREATION, PRODUCTION, OR DELIVERY OF THE SCRIPTS BE LIABLE #
# FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS #
# PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) #
# ARISING OUT OF THE USE OF OR INABILITY TO USE THE SAMPLE SCRIPTS OR DOCUMENTATION, #
# EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES #
#############################################################################################


Function start-m365DomainPasswordExpirationManagement
{
    [cmdletbinding()]

    Param
    (
        #Define other mandatory parameters
        [Parameter(Mandatory = $true)]
        [string]$logFolderPath,
        #Define Microsoft Graph Parameters
        [Parameter(Mandatory = $true, ParameterSetName = "Interactive")]
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
        [ValidateSet("China","Global","USGov","USGovDod")]
        [string]$msGraphEnvironmentName,
        [Parameter(Mandatory = $true, ParameterSetName = "Interactive")]
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
        [string]$msGraphTenantID,
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [string]$msGraphCertificateThumbprint,
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
        [string]$msGraphApplicationID,
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]        
        [string]$msGraphClientSecret,
        [Parameter(Mandatory = $false)]        
        $domainsToModify=@(),
        [Parameter(Mandatory = $true)] 
        [ValidateSet("Enable","Disable")]
        [string]$enableOrDisable="",
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 2147483647)]        
        [int]$expirationDays=$NULL,
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 2147483647)]        
        [int]$expirationNotificationDays=$NULL,
        [Parameter(Mandatory = $false)]        
        [boolean]$includeOnMicrosoftDomains=$false,
        [Parameter(Mandatory = $false)]        
        [boolean]$allDomains=$false,
        [Parameter(Mandatory =$FALSE)]
        [boolean]$allowTelemetryCollection=$TRUE
    )

    $disabledValue = "2147483647"
    $onMicrosoft = "onmicrosoft.com"

    #Create timeline hash table.

    $global:htmlTime = @{}
    $global:htmlTime['StartTime']=Get-Date

    #Create HTML items.

    $global:htmlData = @{}

    $telemetryStartTime = get-universalDateTime

    #Set the window title.

    $windowTitle = ("Start-M365DomainPasswordExpirationManagement")
    $host.ui.RawUI.WindowTitle = $windowTitle

    #Initialize telemetry collection.

    $appInsightAPIKey = "63d673af-33f4-401c-931e-f0b64a218d89"
    $traceModuleName = "M365DomainMgmt"

    if ($allowTelemetryCollection -eq $TRUE)
    {
        start-telemetryConfiguration -allowTelemetryCollection $allowTelemetryCollection -appInsightAPIKey $appInsightAPIKey -traceModuleName $traceModuleName
    }

    $global:ExpirationDomains=[System.Collections.Generic.List[PSObject]]::new()

    #Create telemetry values.

    $telemetryValues = @{}
    $telemetryValues['telemetryM365DomainMgmtVersion']="None"
    $telemetryValues['telemetryMSGraphAuthenticationVersion']="None"
    $telemetryValues['telemetryMSGraphDirectoryVersion']="None"
    $telemetryValues['telemetryEventName']="start-m365DomainPasswordExpirationManagement"

    #Create export table

    $exportNames = @{}
    $exportNames['DomainInfo']="DomainInfo"
    $exportNames['msGraphContext']="MSGraphContext"


    $moduleNames = @{}
    $moduleNames['M365DomainManagement']="M365DomainMGMT"
    $moduleNames['MsGraphAuthentication']="Microsoft.Graph.Authentication"
    $moduleNames['MSGraphDirectory']="Microsoft.Graph.Identity.DirectoryManagement"

    #Create MSGraphHashTable

    $msGraphScopesRequired = @()
    $msGraphScopeDomain = "Domain.ReadWrite.All"
    $msGraphGroupSync = "Group-OnPremisesSyncBehavior.ReadWrite.All"
    $msGraphUserSync = "User-OnPremisesSyncBehavior.ReadWrite.All"
    $msGraphValues = @{}
    $msGraphValues['msGraphEnvironmentName']=$msGraphEnvironmentName
    $msGraphValues['msGraphTenantID']=$msGraphTenantID
    $msGraphValues['msGraphApplicationID']=$msGraphApplicationID
    $msGraphValues['msGraphCertificateThumbprint']=$msGraphCertificateThumbprint
    $msGraphValues['msGraphClientSecret']=$msGraphClientSecret
    $msGraphValues['msGraphScopes']=$msGraphScopesRequired
    $msGraphValues['msGraphAuthenticationType']=$PSCmdlet.ParameterSetName


    #Variables for logging and start log file.

    $global:logFile=$NULL
    $logFileName = "M365DomainMgmt_"+(Get-Date -Format FileDateTime)

    new-logfile -logFileName $logFileName -logFolderPath $logFolderPath

    out-logfile -string "*****************************************************"
    out-logfile -string "Entering Start-M365DomainPasswordExpirationManagement"
    out-logfile -string "*****************************************************"

    $global:htmlTime['PowerShellVersionTest']=Get-Date

    $telemetryValues['telemetryM365DomainMgmtVersion']=test-PowerShellModule -powershellModuleName $moduleNames.M365DomainManagement -powershellVersionTest:$TRUE
    $telemetryValues['telemetryMSGraphAuthenticationVersion']=test-PowerShellModule -powershellModuleName $moduleNames.MsGraphAuthentication -powershellVersionTest:$TRUE
    $telemetryValues['telemetryMSGraphDirectoryVersion']=test-PowerShellModule -powershellModuleName $moduleNames.MSGraphDirectory -powershellVersionTest:$TRUE

    $global:htmlTime['ParameterValidation']=Get-Date

    if ($enableOrDisable -eq "Enable")
    {
        out-logfile -string "Enable Password Expiration operation specified"
        out-logfile -string $expirationDays

        if ($expirationDays -eq 0)
        {
            out-logfile -string "ERROR: Expiration days must be specified when enabling password expiration." -isError:$TRUE
        }

        if ($expirationNotificationDays -eq 0)
        {
            out-logfile -string "ERROR: Expiration notification days must be specified when enabling password expiration." -isError:$TRUE
        }
    }
    elseif ($enableOrDisable -eq "Disable")
    {
        if ($expirationDays -gt 0)
        {
            out-logfile -string "ERROR: Expiration days may not be specified when disabling password expiration." -isError:$TRUE
        }

        if ($expirationNotificationDays -gt 0)
        {
            out-logfile -string "ERROR: Expiration notification days may not be specified when disabling password expiration." -isError:$TRUE
        }
    }
    else 
    {
        out-logfile -string "Disable Password Expiration operation specified"
    }

    out-logfile -string $domainsToModify.getType().name
    out-logfile -string $domainsToModify.getType().baseType.name


    if ($domainsToModify.getType().name -eq "String")
    {
        out-logfile -string "The domains to modify parameter is a string - convert to array."
        $domainsToModify = $domainsToModify -split ","
    }
    elseif ($domainsToModify.getType().BaseType.Name -eq "System.Array")
    {
        out-logfile -string "The domains to modify parameter is an array."
    }
    elseif ($domainsToModify.getType().BaseType.Name -eq "Array")
    {
        out-logfile -string "The domains to modify parameter is an array."
    }
    else 
    {
        out-logfile -string "ERROR: The domains to modify parameter is not a string or array." -isError:$TRUE
    }

    if ($allDomains -eq $TRUE -and $domainsToModify.Count -gt 0)
    {
        out-logfile -string "ERROR: The all domains parameter is set to true and the domains to modify parameter is not empty." -isError:$TRUE
    }
    else 
    {
        out-logfile -string "The following domains will be modified: $($domainsToModify -join ', ')"
    }

    $global:htmlTime['MSGraphConnection']=Get-Date

    $msGraphValues.msGraphScopes += "Domain.ReadWrite.All"

    new-msGraphConnection -msGraphHashTable $msGraphValues -exportFile $exportNames.msGraphContext

    $global:htmlTime['GatherM365Domains']=Get-Date

    if ($allDomains -eq $TRUE)
    {
        out-logfile -string "All domains parameter is set to true - gather all M365 domains."

        try
        {
            $functionM365Domains = get-mgDomain -all -property ID,AuthenticationType,PasswordValidityPeriodInDays,PasswordNotificationWindowInDays,isRoot -errorAction Stop
        }
        catch 
        {
            out-logfile -string $_
            out-logfile -string "ERROR: Unable to gather M365 domains." -isError:$TRUE
        }
    }
    elseif ($domainsToModify.Count -gt 0)
    {
        out-logfile -string "All domains parameter is set to false and the domains to modify parameter is not empty - gather specified M365 domains."

        $functionM365Domains = @()

        foreach ($domain in $domainsToModify)
        {
            try
            {
                $functionM365Domain = get-mgDomain -DomainId $domain -property ID,AuthenticationType,PasswordValidityPeriodInDays,PasswordNotificationWindowInDays,isRoot -errorAction Stop
                $functionM365Domains += $functionM365Domain
            }
            catch 
            {
                out-logfile -string $_
                out-logfile -string "ERROR: Unable to gather M365 domain $domain." -isError:$TRUE
            }
        }
    }
    else 
    {
        out-logfile -string "All domains parameter is set to false and the domains to modify parameter is empty - no domains will be modified."
    }

    $global:htmlTime['ProcessCustomDomainObjects']=Get-Date

    foreach ($domain in $functionM365Domains)
    {
        out-logfile -string "Processing domain $($domain.id)"

        $functionObject = New-Object PSObject -Property ([ordered]@{
            ID = ""
            AuthenticationType = ""
            Current_PasswordValidityPeriodInDays = ""
            New_PasswordValidityPeriodInDays = ""
            Current_PasswordNotificationWindowInDays = ""
            New_PasswordNotificationWindowInDays = ""
            IsRootDomain = ""
            Error = ""
        })

        $functionObject.ID = $domain.id
        $functionObject.AuthenticationType = $domain.AuthenticationType
        $functionObject.Current_PasswordValidityPeriodInDays = $domain.PasswordValidityPeriodInDays
        $functionObject.Current_PasswordNotificationWindowInDays = $domain.PasswordNotificationWindowInDays
        $functionObject.IsRootDomain = $domain.IsRoot

        if ($functionObject.AuthenticationType -eq "Federated")
        {
            $functionObject.error = "FEDERATED_DOMAIN_EXCEPTION: Password validity cannot be set on domains federated for authentication."
        }
        elseif ($includeOnMicrosoftDomains -eq $FALSE)
        {
            if ($functionObject.ID.contains($onMicrosoft))
            {
                out-logfile -string "Domain $($functionObject.ID) is an onmicrosoft.com domain - will be skipped."
                $functionObject.error = "ONMICROSOFT_DOMAIN_EXCEPTION: Domain skipped by administrator specification."
            }
            else
            {
                out-logfile -string "Domain $($functionObject.ID) is not an onmicrosoft.com domain - will be processed."
            }
        }
        elseif ($functionObject.isRootDomain -eq $false)
        {
            out-logfile -string "Domain $($functionObject.ID) is not a root domain and will be skipped."
            $functionObject.error = "ROOT_DOMAIN_EXCEPTION: Expiration cannot be set on a subdomain."
        }
        else 
        {
            out-logfile -string "Domain $($functionObject.ID) is a root domain and can be processed."
        }

        $global:ExpirationDomains.add($functionObject)
    }

    $global:htmlTime['ProcessPasswordExpiration']=Get-Date

    foreach ($domain in $global:ExpirationDomains)
    {
        $setSuccess = $FALSE

        if ($domain.error -eq "")
        {
            if ($enableOrDisable -eq "Enable")
            {
                out-logfile -string "Enabling password expiration for domain $($domain.ID) with expiration days $expirationDays"

                try
                {
                    update-mgDomain -DomainId $domain.ID -PasswordValidityPeriodInDays $expirationDays -PasswordNotificationWindowInDays $expirationNotificationDays -errorAction Stop
                    $setSuccess = $TRUE
                }
                catch 
                {
                    out-logfile -string $_
                    out-logfile -string "ERROR: Unable to enable password expiration for domain $($domain.ID)."
                    $domain.Error = $_.Exception.Message
                }
            }
            elseif ($enableOrDisable -eq "Disable")
            {
                out-logfile -string "Disabling password expiration for domain $($domain.ID)"

                try
                {
                    update-mgDomain -DomainId $domain.ID -PasswordValidityPeriodInDays $disabledValue -PasswordNotificationWindowInDays $disabledValue -errorAction Stop
                    $setSuccess = $TRUE
                }
                catch 
                {
                    out-logfile -string $_
                    out-logfile -string "ERROR: Unable to disable password expiration for domain $($domain.ID)."
                    $domain.Error = $_.Exception.Message
                }
            }
        }
        else
        {
            out-logfile -string "Domain $($domain.ID) has an error - skipping password expiration operation."
        }

        if ($setSuccess -eq $TRUE)
        {
            try
            {
                $updatedDomain = get-mgDomain -DomainId $domain.ID -property ID,AuthenticationType,PasswordValidityPeriodInDays,PasswordNotificationWindowInDays -errorAction Stop
                $domain.New_PasswordValidityPeriodInDays = $updatedDomain.PasswordValidityPeriodInDays
                $domain.New_PasswordNotificationWindowInDays = $updatedDomain.PasswordNotificationWindowInDays
            }
            catch 
            {
                out-logfile -string $_
                out-logfile -string "ERROR: Unable to gather updated M365 domain $($domain.ID)." -isError:$TRUE
            }
        }
        else 
        {
            out-logfile -string "Password expiration was not set for domain $($domain.ID) - skipping gathering updated domain information."
            $domain.New_PasswordValidityPeriodInDays = "ERROR"
            $domain.New_PasswordNotificationWindowInDays = "ERROR"
        }
    }

    $global:htmlTime['ExportDetails']=Get-Date

    out-xmlFile -itemToExport $global:ExpirationDomains -itemNameToExport $exportNames.DomainInfo

    $telemetryEndTime = get-universalDateTime

    [double]$telemetryElapsedSeconds = get-elapsedTime -startTime $telemetryStartTime -endTime $telemetryEndTime

    $telemetryEventProperties = @{
        M365DomainMGMT = $telemetryValues.telemetryEventName    
        M365DomainMGMTVersion = $telemetryValues.telemetryM365DomainMgmtVersion
        MSGraphAuthentication = $telemetryValues.telemetryMSGraphAuthenticationVersion
        MSGraphDirectory = $telemetryValues.telemetryMSGraphDirectoryVersion
    }

    $telemetryEventMetrics = @{
         TotalTime = $telemetryElapsedSeconds  
    }

    send-TelemetryEvent -traceModuleName $traceModuleName -eventName $telemetryValues.telemetryEventName -eventMetrics $telemetryEventMetrics -eventProperties $telemetryEventProperties

    generate-htmlReport 

    Disconnect-MgGraph
}