M365PSProfile.psm1

##############################################################################
# M365 PS Profile
# Installs and Updates the Required PowerShell Modules for M365 Management
##############################################################################

##############################################################################
# Global variable for standard modules
##############################################################################
[array]$global:M365StandardModules = @(
    "ExchangeOnlineManagement",
    "Icewolf.EXO.SpamAnalyze",
    "MicrosoftTeams",
    "Microsoft.Online.SharePoint.PowerShell",
    "PnP.PowerShell",
    "ORCA",
    "O365CentralizedAddInDeployment",
    "M365PSProfile",
    "MSCommerce",
    "WhiteboardAdmin",
    "Microsoft.Graph",
    "Microsoft.Graph.Beta",
    "Microsoft.Entra",
    "Microsoft.Entra.Beta",
    "MicrosoftPlaces",
    "MSIdentityTools",
    "PSMSALNet"
)

##############################################################################
# Get-M365ModulePath
# Returns the Path for the Modules
##############################################################################
Function Get-M365ModulePath {
    <#
        .SYNOPSIS
        Returns the Path for the Modules based ont the Scope Parameter
 
        .DESCRIPTION
        Returns the Path for the Modules based ont the Scope Parameter
 
        .PARAMETER Scope
        Sets the Scope [CurrentUser/AllUsers] for the Installation of the PowerShell Modules. Default value is CurrentUser.
 
        .EXAMPLE
        Get-M365ModulePath -Scope CurrentUser
 
        .EXAMPLE
        Get-M365ModulePath -Scope AllUsers
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>


    PARAM(
        [parameter(mandatory = $false)][ValidateSet("CurrentUser", "AllUsers")][string]$Scope = "CurrentUser"
    )

    If ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows))
    {
        #Windows
        $Personal = [environment]::getfolderpath("mydocuments")
        $ProgramFiles = [environment]::getfolderpath("ProgramFiles")
        If ($PSVersionTable.PSVersion -ge [version]"6.0")
        {
            $Path = "PowerShell"
        } else {
            $Path = "WindowsPowerShell"
        }

        If ($Scope -eq "CurrentUser")
        {
            $LocalUserDir = Join-Path -Path $Personal -ChildPath $Path
            return $LocalUserDir + "\Modules\"
        }

        If ($Scope -eq "AllUsers")
        {
            $AllUsersDir = Join-Path -Path $ProgramFiles -ChildPath $Path
            return $AllUsersDir + "\Modules\"
        }
    } else {
        #Linux / macOS
        If ($Scope -eq "CurrentUser")
        {
            $LocalUserDir = Join-Path -Path $Env:HOME -ChildPath ".local" -AdditionalChildPath "share", "powershell", "Modules"
            return $LocalUserDir
        }

        If ($Scope -eq "AllUsers")
        {
            $AllUsersDir = Join-Path -Path "/usr" -ChildPath "local" -AdditionalChildPath "share", "powershell", "Modules"
            return $AllUsersDir
        }
    }
}

##############################################################################
# Get-M365StandardModules
# Returns the M365StandardModules global variable
##############################################################################
Function Get-M365StandardModule {
    <#
        .SYNOPSIS
        Returns the M365StandardModules global variable.
 
        .DESCRIPTION
        Returns the M365StandardModules global variable which contains the standard modules for M365 Management.
 
        .EXAMPLE
        Get-M365StandardModule
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>


    return $global:M365StandardModules
}

##############################################################################
# Function AsciiArt
##############################################################################
Function Invoke-AsciiArt {
    <#
        .SYNOPSIS
        Generates M365PSProfile AsciiArt
 
        .DESCRIPTION
        Generates M365PSProfile AsciiArt
 
        .EXAMPLE
        Invoke-AsciiArt
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>


    Write-Host " __ __ ____ __ _____ _____ _____ _____ __ _ _ "
    Write-Host "| \/ |___ \ / /| ____| __ \ / ____| __ \ / _(_) | "
    Write-Host "| \ / | __) |/ /_| |__ | |__) | (___ | |__) | __ ___ | |_ _| | ___ "
    Write-Host "| |\/| ||__ <| '_ \___ \| ___/ \___ \| ___/ '__/ _ \| _| | |/ _ \"
    Write-Host "| | | |___) | (_) |__) | | ____) | | | | | (_) | | | | | __/"
    Write-Host "|_| |_|____/ \___/____/|_| |_____/|_| |_| \___/|_| |_|_|\___|"
    Write-Host "Version:" $MyInvocation.MyCommand.ScriptBlock.Module.Version
}

##############################################################################
# Add-M365PSProfile
# Add M365PSProfile, if no PowerShell Profile exists
##############################################################################
Function Add-M365PSProfile {
    <#
        .SYNOPSIS
        Add PowerShell Profile with M365PSProfile setup
 
        .DESCRIPTION
        Add PowerShell Profile with M365PSProfile setup.
 
        Needs to be executed separately for PowerShell v5 and v7.
 
        .PARAMETER ProfileType
        Specifies which PowerShell Profile to modify:
        - CurrentUserCurrentHost (default) - $PROFILE.CurrentUserCurrentHost
        - CurrentUserAllHosts - $PROFILE.CurrentUserAllHosts
        - AllUsersCurrentHost - $PROFILE.AllUsersCurrentHost
        - AllUsersAllHosts - $PROFILE.AllUsersAllHosts
 
        .EXAMPLE
        Add-M365PSProfile
 
        .EXAMPLE
        Add-M365PSProfile -ProfileType AllUsersCurrentHost
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
        param(
            [parameter(mandatory = $false)][ValidateSet("CurrentUserCurrentHost", "CurrentUserAllHosts", "AllUsersCurrentHost", "AllUsersAllHosts")][string]$ProfileType = "CurrentUserCurrentHost"
        )

    $ProfilePath = $PROFILE.$ProfileType

    $M365PSProfileContent = @"
 
#M365PSProfile: Install or updates the default Modules (what we think every M365 Admin needs) in the CurrentUser Scope
Import-Module -Name M365PSProfile
Install-M365Module
"@


    If (-not(Test-Path -Path $ProfilePath)) {
        #No Profile found
        Write-Host "No PowerShell Profile ($ProfileType) exists. A new Profile with the M365PSProfile setup is created."
        #Ensure the directory exists
        $ProfileDir = Split-Path -Path $ProfilePath -Parent
        If (-not(Test-Path -Path $ProfileDir)) {
            New-Item -ItemType Directory -Path $ProfileDir -Force | Out-Null
        }
        $M365PSProfileContent | Out-File -FilePath $ProfilePath -Encoding utf8 -Force
    } else {
        #Profile found
        $ProfileContent = Get-Content -Path $ProfilePath -Encoding utf8
        $Match = $ProfileContent | Where-Object {$_ -match "Install-M365Module"}
        If ($Null -ne $Match)
        {
            #M365PSProfile already in Profile
            Write-Host "PowerShell Profile ($ProfileType) already exists. M365PSProfile is already in the Profile." -ForegroundColor Yellow
        } else {
            #M365PSProfile not in Profile
            Write-Host "PowerShell Profile ($ProfileType) already exists. Adding M365PSProfile to it" -ForegroundColor Yellow
            Add-Content -Path $ProfilePath -Value $M365PSProfileContent -Encoding utf8
        }
    }
}

##############################################################################
# Uninstall-M365Modules
# Remove Modules in -Modules Parameter
##############################################################################
Function Uninstall-M365Module {
    <#
        .SYNOPSIS
        Uninstall M365 PowerShell Modules
 
        .DESCRIPTION
        Uninstall of all defined M365 modules
 
        .PARAMETER Modules
        Array of Module Names that will be uninstalled. Default value are the default modules (see Get-M365StandardModule) or an Array with the Modules to uninstall.
 
        .PARAMETER Scope
        Sets the Scope [CurrentUser/AllUsers] for the Installation of the PowerShell Modules. Default value is CurrentUser.
 
        .PARAMETER Repository
        [string]Repository specifies which PowerShell Repository should be used [Default is PSGallery]
 
        .PARAMETER FileMode
        [switch]FileMode uses the File System to remove the Modules
 
        .EXAMPLE
        Uninstall-M365Modules
 
        .EXAMPLE
        Uninstall-M365Modules -Modules "Az","MSOnline","PnP.PowerShell","Microsoft.Graph" -Scope CurrentUser
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>


    param (
        [Parameter(Mandatory = $false)][array]$Modules = $global:M365StandardModules,
        [parameter(mandatory = $false)][ValidateSet("CurrentUser", "AllUsers")][string]$Scope = "CurrentUser",
        [parameter(mandatory = $false)][switch]$FileMode = $false
    )

    If ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows))
    {
        #Windows
        $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) -ErrorAction SilentlyContinue
        $IsAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    #Loaded Assemblys
    #$LoadedAssemblys = [System.AppDomain]::CurrentDomain.GetAssemblies()

    foreach ($Module in $Modules) {

        #Unload Module
        If ($Module -ne "M365PSProfile")
        {
            Get-Module -Name $Module | Remove-Module -Force -ErrorAction SilentlyContinue
        }

        #Check if Module is Installed
        [Array]$InstalledModules = Get-InstalledPSResource -Name $Module -Scope $Scope -ErrorAction SilentlyContinue | Sort-Object Version -Descending

        if ($InstalledModules) {
            # Module found
            if (($IsAdmin -eq $false) -and ($Scope -eq "AllUsers")) {
                Write-Host "WARNING: PS must be running <As Administrator> to uninstall the Module" -ForegroundColor Red
            } else {
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all $Module Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "$Module" -Recurse | Remove-Item -Force -Recurse
                } else {
                    try {
                        Write-Host "Uninstall Module: $Module $($InstalledModules.Version.ToString())" -ForegroundColor Yellow
                        Uninstall-PSResource -Name $Module -Scope $Scope -SkipDependencyCheck -ErrorAction Stop
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error occured. Try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }

            #If AZ also Uninstall all AZ.* Modules
            If ($Module -eq "AZ")
            {
                Write-Host "Uninstall AZ.* Modules" -ForegroundColor Yellow
                #Get-InstalledPSResource -Name "AZ.*" -Scope $Scope -ErrorAction SilentlyContinue | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                $InstalledAZModules = Get-InstalledPSResource -Name "AZ.*" -Scope $Scope -ErrorAction SilentlyContinue
                Foreach ($AZModule in $InstalledAZModules)
                {
                    #FileMode
                    If ($FileMode -eq $true)
                    {
                        #Write-Host "Using FileMode. Remove Module: $($AZModule.Name)" -ForegroundColor Yellow
                        $ModulesPath = Get-M365ModulePath -Scope $Scope
                        Get-ChildItem -Path $ModulesPath -Filter "AZ.*" -Recurse | Remove-Item -Force -Recurse
                    } else {
                        try {
                            #Write-Host "Uninstall Module: $($AZModule.Name) $($AZModule.Version.ToString())" -ForegroundColor Yellow
                            Uninstall-PSResource -Name $AZModule.Name -Scope $Scope -SkipDependencyCheck -WarningAction SilentlyContinue
                        } catch [System.ArgumentException] {
                            $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                            if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                            {
                                Write-Host "Error occured. Try using -FileMode" -ForegroundColor Red
                            }
                        }
                    }
                }
            }

            #If Microsoft.Entra also Uninstall all Microsoft.Entra.* Modules
            If ($Module -eq "Microsoft.Entra")
            {
                Write-Host "Uninstall Microsoft.Entra.* Modules" -ForegroundColor Yellow
                #Get-InstalledPSResource -Name "Microsoft.Entra.*" -Scope $Scope -ErrorAction SilentlyContinue | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                $InstalledEntraModules = Get-InstalledPSResource -Name "Microsoft.Entra.*" -Scope $Scope -ErrorAction SilentlyContinue
                Foreach ($EntraModule in $InstalledEntraModules)
                {
                    #FileMode
                    If ($FileMode -eq $true)
                    {
                        #Write-Host "Using FileMode. Remove Module: $($EntraModule.Name)" -ForegroundColor Yellow
                        $ModulesPath = Get-M365ModulePath -Scope $Scope
                        Get-ChildItem -Path $ModulesPath -Filter "Microsoft.Entra.*" -Recurse | Remove-Item -Force -Recurse
                    } else {
                        try {
                            #Write-Host "Uninstall Module: $($EntraModule.Name) $($EntraModule.Version.ToString())" -ForegroundColor Yellow
                            Uninstall-PSResource -Name $EntraModule.Name -Scope $Scope -SkipDependencyCheck -WarningAction SilentlyContinue
                        } catch [System.ArgumentException] {
                            $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                            if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                            {
                                Write-Host "Error occured. Try using -FileMode" -ForegroundColor Red
                            }
                        }
                    }
                }
            }

            #If Microsoft.Graph also Uninstall all Microsoft.Graph.* Modules
            If ($Module -eq "Microsoft.Graph")
            {
                #FileMode
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all Microsoft.Graph.* Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "Microsoft.Graph.*" -Recurse | Remove-Item -Force -Recurse
                } else {
                    try {
                        Write-Host "Uninstall Microsoft.Graph.* Modules" -ForegroundColor Yellow
                        Get-InstalledPSResource -Name "Microsoft.Graph.*" -Scope $Scope -ErrorAction SilentlyContinue | Where-Object {$_.Name -notmatch "Microsoft.Graph.Beta"} | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error occured. Try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }

            #If Microsoft.Graph.Beta also Uninstall all Microsoft.Graph.Beta.* Modules
            If ($Module -eq "Microsoft.Graph.Beta")
            {
                #FileMode
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all Microsoft.Graph.Beta* Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "Microsoft.Graph.Beta*" -Recurse | Remove-Item -Force -Recurse
                } else {
                    try {
                        Write-Host "Uninstall Microsoft.Graph.Beta.* Modules" -ForegroundColor Yellow
                        Get-InstalledPSResource -Name "Microsoft.Graph.Beta*" -Scope $Scope -ErrorAction SilentlyContinue | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error Occured try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }

        } else {
            #Module Notfound
            If ($FileMode -eq $true)
            {
                Write-Host "Using FileMode. Remove all $Module Modules" -ForegroundColor Yellow
                $ModulesPath = Get-M365ModulePath -Scope $Scope
                Get-ChildItem -Path $ModulesPath -Filter "$Module" -Recurse | Remove-Item -Force -Recurse
            }

            #If AZ also Uninstall all AZ.* Modules
            If ($Module -eq "AZ")
            {
                #FileMode
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all AZ.* Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "AZ.*" -Recurse | Remove-Item -Force -Recurse
                } else {
                    try {
                        Write-Host "NO AZ Root Module. Uninstall AZ.* Modules" -ForegroundColor Yellow
                        Get-InstalledPSResource -Name "AZ.*" -Scope $Scope -ErrorAction SilentlyContinue | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error Occured try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }

            #If Microsoft.Entra also Uninstall all Microsoft.Entra.* Modules
            If ($Module -eq "Microsoft.Entra")
            {
                #FileMode
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all Microsoft.Entra.* Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "MicrosoftEntra.*" -Recurse | Remove-Item -Force -Recurse
                } else {
                    try {
                        Write-Host "NO Entra Root Module. Uninstall Microsoft.Entra.* Modules" -ForegroundColor Yellow
                        Get-InstalledPSResource -Name "Entra.*" -Scope $Scope -ErrorAction SilentlyContinue | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error Occured try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }

            #If Microsoft.Graph also Uninstall all Microsoft.Graph.* Modules
            If ($Module -eq "Microsoft.Graph")
            {
                #FileMode
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all Microsoft.Graph.* Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "Microsoft.Graph.*" -Recurse | Remove-Item -Force -Recurse
                } else {
                    Try {
                        Write-Host "NO Microsoft.Graph Root Module. Uninstall Microsoft.Graph.* Modules" -ForegroundColor Yellow
                        Get-InstalledPSResource -Name "Microsoft.Graph.*" -Scope $Scope -ErrorAction SilentlyContinue | Where-Object {$_.Name -notmatch "Microsoft.Graph.Beta"} | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error Occured try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }

            #If Microsoft.Graph.Beta also Uninstall all Microsoft.Graph.Beta.* Modules
            If ($Module -eq "Microsoft.Graph.Beta")
            {
                #FileMode
                If ($FileMode -eq $true)
                {
                    Write-Host "Using FileMode. Remove all Microsoft.Graph.Beta* Modules" -ForegroundColor Yellow
                    $ModulesPath = Get-M365ModulePath -Scope $Scope
                    Get-ChildItem -Path $ModulesPath -Filter "Microsoft.Graph.Beta*" -Recurse | Remove-Item -Force -Recurse
                } else {
                    try {
                        Write-Host "NO Microsoft.Graph.Beta Module. Uninstall Microsoft.Graph.Beta.* Modules" -ForegroundColor Yellow
                        Get-InstalledPSResource -Name "Microsoft.Graph.Beta*" -Scope $Scope -ErrorAction SilentlyContinue | Uninstall-PSResource -Scope $Scope -SkipDependencyCheck
                    } catch [System.ArgumentException] {
                        $FullyQualifiedErrorId = $error[0].FullyQualifiedErrorId
                        if ($FullyQualifiedErrorId -eq "ErrorDeletingDirectory,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource")
                        {
                            Write-Host "Error Occured try using -FileMode" -ForegroundColor Red
                        }
                    }
                }
            }
        }
    }
}

##############################################################################
# Remove existing PS Connections
##############################################################################
Function Disconnect-All {
    <#
        .SYNOPSIS
        Disconnect all Connections to Microsoft 365 Services
 
        .DESCRIPTION
        Disconnect all Connections of the Modules MicrosoftTeams, ExchangeOnlineManagement, Microsoft.Online.SharePoint.PowerShell, Microsoft.Graph and removes remote PS Sessions
 
        .EXAMPLE
        Disconnect-All
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>


    Get-PSSession | Remove-PSSession
    Disconnect-MicrosoftTeams -ErrorAction SilentlyContinue
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null

    If (Get-Module -Name "Microsoft.Online.SharePoint.PowerShell")
    {
        Disconnect-SPOService -ErrorAction SilentlyContinue
    }

    If (Get-Module -Name "ExchangeOnlineManagement")
    {
        Disconnect-ExchangeOnline -confirm:$false -ErrorAction SilentlyContinue
    }

    If (Get-Module -Name "PnP.PowerShell")
    {
        Disconnect-PnPOnline -ErrorAction SilentlyContinue
    }
}

#############################################################################
# Set-WindowTitle Function
#############################################################################
Function Set-WindowTitle {
    <#
        .SYNOPSIS
        Set the Window Title
 
        .DESCRIPTION
        Set the Window Title
 
        .EXAMPLE
        Set-WindowTitle
 
        .EXAMPLE
        Set-WindowTitle -Title "My Title"
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
    #>


    [CmdletBinding(SupportsShouldProcess)]
    PARAM (
        [string]$Title = "Windows PowerShell"
    )
    If ($PSCmdlet.ShouldProcess($Title))
    {
        $host.ui.RawUI.WindowTitle = $Title
    }
}

##############################################################################
# Main Program
##############################################################################

Function Install-M365Module {
    <#
        .SYNOPSIS
        M365PSProfile installs and keeps the PowerShell Modules needed for Microsoft 365 Management up to date.
        It provides a simple way to add it to the PowerShell Profile
 
        .DESCRIPTION
        M365PSProfile installs and keeps the PowerShell Modules needed for Microsoft 365 Management up to date.
        It provides a simple way to add it to the PowerShell Profile
 
        .PARAMETER Modules
        [array]$Modules = @(<ModuleName1>,<Modulename2>)
        [array]$Modules = @("AZ", "MSOnline", "AzureADPreview", "ExchangeOnlineManagement", "Icewolf.EXO.SpamAnalyze", "MicrosoftTeams", "Microsoft.Online.SharePoint.PowerShell", "PnP.PowerShell" , "ORCA", "O365CentralizedAddInDeployment", "MSCommerce", "WhiteboardAdmin", "Microsoft.Graph", "Microsoft.Graph.Beta", "MSAL.PS", "MSIdentityTools" )
 
        .PARAMETER Scope
        Sets the Scope [CurrentUser/AllUsers] for the Installation of the PowerShell Modules
 
        .PARAMETER AsciiArt
        [bool]AsciiArt controls the AsciiArt Screen at the Start
 
        .PARAMETER $RunInVSCode
        [bool]$RunInVSCode controls if the Script will run in VSCode [Default is $false]
 
        .PARAMETER Repository
        [string]Repository specifies which PowerShell Repository should be used [Default is PSGallery]
 
        .PARAMETER KeepMultipleVersions
        [switch]KeepMultipleVersions keeps multiple Versions of the Modules installed [Default is to uninstall old Versions]
 
        .LINK
        https://github.com/fabrisodotps1/M365PSProfile
 
        .EXAMPLE
        #Installs and updates the Default Modules in CurrentUser Scope
        Install-M365Modules
 
        .EXAMPLE
        #Installs and updates the specified Modules
        Install-M365Modules -Modules @("ExchangeOnlineManagement", "MicrosoftTeams", "Microsoft.Online.SharePoint.PowerShell", "PnP.PowerShell") -Scope [CurrentUser|AllUsers]
 
        .EXAMPLE
        #Installs and updates the specified Modules without showing AsciiArt at the Start
        Install-M365Modules -Modules @("ExchangeOnlineManagement", "MicrosoftTeams", "Microsoft.Online.SharePoint.PowerShell", "PnP.PowerShell") -Scope [CurrentUser|AllUsers] -AsciiArt $False
 
        .EXAMPLE
        #Installs and updates the Default Modules in CurrentUser Scope and use a custom repository called "MyRepo"
        Install-M365Modules -Repository "MyRepo"
    #>


    #Parameter for the Module
    param(
        [parameter(mandatory = $false)][array]$Modules = $global:M365StandardModules,
        [parameter(mandatory = $false)][ValidateSet("CurrentUser", "AllUsers")][string]$Scope = "CurrentUser",
        [parameter(mandatory = $false)][bool]$AsciiArt = $true,
        [parameter(mandatory = $false)][bool]$RunInVSCode = $false,
        [parameter(mandatory = $false)][string]$Repository = "PSGallery",
        [parameter(mandatory = $false)][switch]$KeepMultipleVersions = $false
    )

    #Check if it is running in VSCode
    if ($env:TERM_PROGRAM -eq 'vscode') {
        If ($RunInVSCode -eq $false) {
            Exit
        }
    }

    If ($AsciiArt -eq $true) {
        #Show AsciArt
        Invoke-AsciiArt
    }

    If ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows))
    {
        #Windows
        $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) -ErrorAction SilentlyContinue
        $IsAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    Import-Module  Microsoft.PowerShell.PSResourceGet

    if($Repository -eq "PSGallery") {
        $PSGallery = Get-PSResourceRepository -Name PSGallery
        If ($PSGallery.Trusted -eq $false) {
            Write-Host "Warning: PSGallery is not Trusted (Get-PSResourceRepository -Name PSGallery)" -ForegroundColor Yellow
            Write-Host "Set PSGallery to Trusted (Set-PSResourceRepository -Name PSGallery -Trusted:$true)" -ForegroundColor Yellow
            Set-PSResourceRepository -Name PSGallery -Trusted:$true
        }
    }

    #Check if VSCode or PowerShell is running
    [array]$process = Get-Process | Where-Object { $_.ProcessName -eq "powershell" -or $_.ProcessName -eq "pwsh" -or $_.ProcessName -eq "code" }

    If ($process.count -gt 1) {
        Write-Host "PowerShell or Visual Studio Code running? Please close it, Modules in use can't be updated..." -ForegroundColor Yellow
        $process

        #count back from 5 to 1 and start the update
        5..1 | ForEach-Object {
        Write-Host "M365PSProfile Update starts in $_ seconds... (Hit Ctrl+C to cancel)" -ForegroundColor Yellow
        Start-Sleep -Seconds 1
        }
    }

    Write-Host "Checking Modules..."
    #Check Microsoft.PowerShell.PSResourceGet
    #Can't uninstall loaded DLL's so you have to uninstall next time you start PowerShell
    #[System.AppDomain]::CurrentDomain.GetAssemblies() | where {$_.Location -match "Microsoft.PowerShell.PSResourceGet"}
    $Module = "Microsoft.PowerShell.PSResourceGet"
    If ($Scope -eq "CurrentUser")
    {
        [Array]$InstalledModules = Get-InstalledPSResource -Name $Module -Scope $Scope -ErrorAction SilentlyContinue | Sort-Object Version -Descending -ErrorAction SilentlyContinue
        If ($Null -eq $InstalledModules)
        {
            #Module not found - try to find it in AllUsers Scope
            [Array]$InstalledModules = Get-InstalledPSResource -Name $Module -Scope "AllUsers" -ErrorAction SilentlyContinue | Sort-Object Version -Descending -ErrorAction SilentlyContinue
                If ($Null -eq $InstalledModules)
                {
                    # Since PowerShell v7.6.0 the PSResourceGet Module is included in PowerShell. If it is not found in the CurrentUser or AllUsers Scope, it should be loaded as a system module.
                    [Array]$InstalledModules = Get-Module -Name Microsoft.PowerShell.PSResourceGet -ListAvailable -ErrorAction SilentlyContinue | Sort-Object Version -Descending -ErrorAction SilentlyContinue
                    Write-Host "Checking Module: $Module $($InstalledModules[0].Version.ToString())" -ForegroundColor Green
                } else {
                    Write-Host "Checking Module: $Module $($InstalledModules[0].Version.ToString())" -ForegroundColor Green
                }
        } else {
            Write-Host "Checking Module: $Module $($InstalledModules[0].Version.ToString())" -ForegroundColor Green
        }
    }

    If ($InstalledModules.Count -gt 1)
    {
        $Version = $InstalledModules[$InstalledModules.Count - 1].Version
        Write-Host "Uninstall Module $Module $Version" -ForegroundColor Yellow
        #Uninstall-PSResource -Name $Module -Scope $Scope -Version $Version -SkipDependencyCheck
        Uninstall-M365Module -Module $Module -Scope $Scope -FileMode
    } else {
        If ($InstalledModules.Count -eq 0)
        {
            #PSResourceGet Module not found
            Write-Host "Install PSResourceGet: Install-Module -Name Microsoft.PowerShell.PSResourceGet -Scope CurrentUser" -ForegroundColor Red
            #Write-Host "Module $Module not found. Try to install..." -ForegroundColor Yellow
            #Install-PSResource -Name $Module -Scope $Scope -TrustRepository -WarningAction SilentlyContinue -Repository $Repository
        } else {
            #Only one Version found
            [System.Version]$InstalledModuleVersion = $($InstalledModules.Version.ToString())

            #Get Module from PowerShell Gallery (or another repository if specified)
            $PSGalleryModule = Find-PSResource -Name $Module -Repository $Repository
            $PSGalleryVersion = $PSGalleryModule.Version.ToString()

            #Version Check
            If ($PSGalleryVersion -gt $InstalledModuleVersion)
            {
                Write-Host "Install newest Module $Module $PSGalleryVersion" -ForegroundColor Yellow
                Install-PSResource -Name $Module -Scope $Scope -TrustRepository -WarningAction SilentlyContinue -Repository $Repository
            }
        }
    }

    Foreach ($Module in $Modules) {
        #Get Array of installed Modules
        [Array]$InstalledModules = Get-InstalledPSResource -Name $Module -Scope $Scope -ErrorAction SilentlyContinue | Sort-Object Version -Descending

        If ($Null -eq $InstalledModules) {
            #Module not found
            Write-Host "$Module Module not found. Try to install..."
            If ($IsAdmin -eq $false -and $Scope -eq "AllUsers") {
                Write-Host "WARNING: PS must be running <As Administrator> to install the Module" -ForegroundColor Red
            } else {
                #Get Module from PowerShell Gallery
                $PSGalleryModule = Find-PSResource -Name $Module -Repository $Repository #-Prerelease
                $PSGalleryVersion = $PSGalleryModule.Version.ToString()
                Write-Host "Install newest Module $Module $PSGalleryVersion" -ForegroundColor Yellow

                Install-PSResource -Name $Module -Scope $Scope -TrustRepository -WarningAction SilentlyContinue -Repository $Repository #-Prerelease
            }
        } else {
            #Module found

            #Get Module from PowerShell Gallery (or another repository if specified)
            $PSGalleryModule = Find-PSResource -Name $Module -Repository $Repository #-Prerelease
            [System.Version]$PSGalleryVersion = $PSGalleryModule.Version.ToString()

            #Check if Multiple Modules are installed
            If (($InstalledModules.count) -gt 1) {

                If ($KeepMultipleVersions -eq $false)
                {
                    #count back from 5 to 1 and remove old Versions
                    5..1 | ForEach-Object {
                        Write-Host "WARNING: $Module > Multiple Versions found. Uninstall starts in $_ seconds... (Hit Ctrl+C to cancel)" -ForegroundColor Yellow
                        Start-Sleep -Seconds 1
                    }
                    #Uninstall all Modules
                    Write-Host "Uninstall Module"
                    #Uninstall-PSResource -Name $Module -Scope $Scope -SkipDependencyCheck
                    Uninstall-M365Module -Module $Module -Scope $Scope -FileMode
                }

                #Install newest Module
                Write-Host "Install newest Module $Module $PSGalleryVersion" -ForegroundColor Yellow
                Install-PSResource -Name $Module -Scope $Scope -TrustRepository -WarningAction SilentlyContinue -Repository $Repository #-Prerelease

            } else {
                #Only one Module found
                [System.Version]$InstalledModuleVersion = $($InstalledModules.Version.ToString())

                #Version Check
                If ($PSGalleryVersion -gt $InstalledModuleVersion) {
                    #Uninstall Module
                    Write-Host "Uninstall Module: $Module $($InstalledModules.Version.ToString())" -ForegroundColor Yellow
                    #Uninstall-PSResource -Name $Module -Scope $Scope -SkipDependencyCheck
                    Uninstall-M365Module -Module $Module -Scope $Scope -FileMode

                    #If AZ also Uninstall all AZ.* Modules
                    If ($Module -eq "AZ")
                    {
                        Write-Host "Uninstall AZ.* Modules" -ForegroundColor Yellow
                        #Uninstall-PSResource AZ.* -Scope $Scope -SkipDependencyCheck
                        #Get-InstalledPSResource -Name "AZ.*" -Scope $Scope | Uninstall-PSResource -SkipDependencyCheck
                        Uninstall-M365Module -Module "AZ" -Scope $Scope -FileMode
                    }

                    #If Microsoft.Graph also Uninstall all Microsoft.Graph.* Modules
                    If ($Module -eq "Microsoft.Graph")
                    {
                        Write-Host "Uninstall Microsoft.Graph.* Modules" -ForegroundColor Yellow
                        #Get-InstalledPSResource -Name "Microsoft.Graph.*" -Scope $Scope | Where-Object {$_.Name -notmatch "Microsoft.Graph.Beta"} | Uninstall-PSResource -SkipDependencyCheck
                        Uninstall-M365Module -Module "Microsoft.Graph" -Scope $Scope -FileMode
                    }

                    #If Microsoft.Graph.Beta also Uninstall all Microsoft.Graph.Beta.* Modules
                    If ($Module -eq "Microsoft.Graph.Beta")
                    {
                        Write-Host "Uninstall Microsoft.Graph.Beta.* Modules" -ForegroundColor Yellow
                        #Get-InstalledPSResource -Name "Microsoft.Graph.Beta*" -Scope $Scope | Uninstall-PSResource -SkipDependencyCheck
                        Uninstall-M365Module -Module "Microsoft.Graph.Beta" -Scope $Scope -FileMode
                    }

                    #Install Module
                    Write-Host "Install Module: $Module $PSGalleryVersion" -ForegroundColor Yellow
                    Install-PSResource -Name $Module -Scope $Scope -TrustRepository -WarningAction SilentlyContinue -Repository $Repository #-Prerelease
                } else {
                    #Write Module Name
                    Write-Host "Checking Module: $Module $($InstalledModules.Version.ToString())" -ForegroundColor Green
                }
            }
        }
    }
}

##############################################################################
# Import Module
##############################################################################
If (-not(Test-Path -Path $Profile))
{
    Write-Host "No PowerShell Profile exists. You can add the M365PSProfile Update check with Add-M365PSProfile" -ForegroundColor Yellow
} else {
    $Content = Get-Content -Path $Profile -Encoding utf8
    If ($Content -match "Install-M365Module")
    {
        #Match found
    } else {
        #No Match found
        Write-Host  "You have a PowerShell Profile. You can add the M365PSProfile Update check with Add-M365PSProfile" -ForegroundColor Yellow
    }
}