Connect-O365.ps1

<#PSScriptInfo
 
.TITLE Connect-O365
 
.VERSION 1.5.6
 
.GUID a3515355-c4b6-4ab8-8fa4-2150bbb88c96
 
.AUTHOR Jos Verlinde [MSFT]
 
.COMPANYNAME Microsoft
 
.COPYRIGHT
 
.TAGS O365 RMS 'Exchange Online' 'SharePoint Online' 'Skype for Business' 'PnP-Powershell' 'Office 365'
   
.LICENSEURI
 
.PROJECTURI mailto:jos.verlinde@microsoft.com
 
.ICONURI https://onedrive.live.com/download?resid=5DF749BB977BF4D6!393423&authkey=!ADcrsZkNg64G36w&v=3&ithint=photo%2cpng
 
.EXTERNALMODULEDEPENDENCIES MSOnline, Microsoft.Online.SharePoint.PowerShell, AADRM, OfficeDevPnP.PowerShell.V16.Commands
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
v1.5.5 added -close and fixed parameter sets, added inline help to parameters
v1.5.5 Fixed Language for MSOnline / AAD module
v1.5 Add installation of dependent modules
v1.4 Corrected bug wrt compliance search, remove prior created remote powershell sessions
V1.3 Add dependend module information
V1.2 add try-catch for SPO PNP Powershell, as that is less common
V1.1 initial publication to scriptcenter
#>


<#
.Synopsis
   Connect to Office 365 and get ready to administer all services.
   Includes installation of PowerShell modules 1/4/2016
.DESCRIPTION
   Connect to Office 365 and most related services and get ready to administer all services.
   The commandlet supports saving your administrative credentials in a safe manner so that it can be used in unattended files
 
   Allows Powershell administration of : O365, Azure AD , Azure RMS, Exchange Online, SharePoint Online including PNP Powershell
       
.EXAMPLE
   connect-O365 -Account 'admin@contoso.com' -SharePoint
 
.EXAMPLE
   connect-O365 -Account 'admin@contoso.com' -SPO -EXO -Skype -Compliance -AADRM
 
.EXAMPLE
   #close any previously opened PS remote sessions (Exchange , Skype , Compliance Center)
   connect-O365 -close
 
.EXAMPLE
   #Connect to MSOnline, and store securly store the credentials
   connect-O365 -Account 'admin@contoso.com' -Persist:$false
.EXAMPLE
 
   connect-O365 -Account 'admin@contoso.com'
   #retrieve credentials for use in other cmdlets
   $Creds = Get-myCreds 'admin@contoso.com'
 
.EXAMPLE
   connect-O365 -install
    
#>


[CmdletBinding()]
[Alias("COL")]
[Alias("Connect-Office365")]
[OutputType([int])]
Param
(
    [CmdletBinding(DefaultParametersetName=”Admin")] 

    # Specify the (Admin) Account to authenticate with
    [Parameter(ParameterSetName="Admin",Mandatory=$false,Position=0)]
    [string]$Account,
        
    # Save the account credentials for later use
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [switch]$Persist = $false, 

    #Connect to Azure AD aka MSOnline
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [Alias("AzureAD")] 
    [switch]$AAD = $true, 

    #Connect to Exchange Online
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [Alias("EXO")] 
    [switch]$Exchange = $false, 

    #Connect to Skype Online
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [Alias("CSO")] 
    [switch]$Skype = $false, 
    
    #Connecto to SharePoint Online
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [Alias("SPO")] 
    [switch]$SharePoint = $false, 
        
    #Load and connecto to the O365 Compliance center
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [switch]$Compliance = $false,

    #Connect to Azure Rights Management
    [Parameter(ParameterSetName="Admin",Mandatory=$false)]
    [Alias("AZRMS")] 
    [Alias("RMS")]
    [switch]$AADRM = $false,

    
    
    <# Add seperate parameterset #>

    #Close all open Connections
    [Parameter(ParameterSetName="Close",Mandatory=$false)]
    [switch]$Close = $false,

    <# Add seperate parameterset #>

    #Download and Install the supporting Modules
    [Parameter(ParameterSetName="Install",Mandatory=$true)]
    [switch]$Install,
    #Specify the Language code of the modules to download ( not applicable to all modules)
    #Sample : -Language NL
    [Parameter(ParameterSetName="Install",Mandatory=$false)]
    [Alias("Lang")] 
    $Language = 'EN',
    #Specify the Language-Locale code of the modules to download ( not applicable to all modules)
    #Sample : -Language NL-NL
    #Sample : -Language EN-US
    [Parameter(ParameterSetName="Install",Mandatory=$false)]
    $LangCountry = 'EN-US',
    #Specify where to download the installable MSI and EXE modules to
    [Parameter(ParameterSetName="Install",Mandatory=$false)]
    $Folder = $null, #'C:\Users\Jos\Downloads',

# [Parameter(ParameterSetName="Install",Mandatory=$false)]
# $InstallPreview = $true,

    #Not in a specific parameterset

    #Force asking for, and optionally force the Perstistance of the credentials.
    [Parameter(Mandatory=$false)]
    [switch]$Force = $false


)

function global:Store-myCreds ($username){
    $Credential = Get-Credential -Credential $username
    $Store = "$env:USERPROFILE\creds\$USERNAME.txt"
    MkDir "$env:USERPROFILE\Creds" -ea 0 | Out-Null
    $Credential.Password | ConvertFrom-SecureString | Set-Content $store
    Write-Verbose "Saved credentials to $store"
    return $Credential 
 }

function global:Get-myCreds ($UserName , [switch]$Persist, [switch]$Force=$false){
    $Store = "$env:USERPROFILE\creds\$USERNAME.txt"
    if ( (Test-Path $store) -AND $Force -eq $false ) {
        #use a stored password if found , unless -force is used to ask for and store a new password
        Write-Verbose "Retrieved credentials from $store"
        $Password = Get-Content $store | ConvertTo-SecureString
        $Credential = New-Object System.Management.Automation.PsCredential($UserName,$Password)
        return $Credential
    } else {
        if ($persist -and -not [string]::IsNullOrEmpty($UserName)) {
            $admincredentials  = Store-myCreds $UserName
            return $admincredentials
        } else {
            return Get-Credential -Credential $username
        }
    }
 }
 

if($Close) {
    write-verbose "Closing open sessions for Exchange Online, Skype and Compliance Center"
    #Close Existing (remote Powershell Sessions)

    Get-PSSession -Name "Exchange Online" -ea SilentlyContinue | Remove-PSSession 
    Get-PSSession -Name "Compliance Center"  -ea SilentlyContinue | Remove-PSSession 
    Get-PSSession -Name "Skype Online" -ea SilentlyContinue| Remove-PSSession 
    
    return
}


if (-NOT $install) {

    $admincredentials = Get-myCreds $account -Persist:$Persist -Force:$Force
    if ($admincredentials -eq $null){ throw "A valid Tenant Admin Account is required." } 



    if ( $AAD) {
        write-verbose "Connecting to Azure AD"
        #Imports the installed Azure Active Directory module.
        Import-Module MSOnline -Verbose:$false 
        if (-not (Get-Module MSOnline ) ) { Throw "Module not installed"}
        #Establishes Online Services connection to Office 365 Management Layer.
        Connect-MsolService -Credential $admincredentials
    }

    if ($Skype ){
        write-verbose "Connecting to Skype Online"
        #Imports the installed Skype for Business Online services module.
        Import-Module SkypeOnlineConnector -Verbose:$false  -Force 

        #Remove prior Session
        Get-PSSession -Name "Skype Online" -ea SilentlyContinue| Remove-PSSession 

        #Create a Skype for Business Powershell session using defined credential.
        $SkypeSession = New-CsOnlineSession -Credential $admincredentials -Verbose:$false
        $SkypeSession.Name="Skype Online"

        #Imports Skype for Business session commands into your local Windows PowerShell session.
        Import-PSSession -Session  $SkypeSession -AllowClobber -Verbose:$false

    }


    If ($SharePoint) {
        write-verbose "Connecting to SharePoint Online"
        if (!$AAD) {
            Throw "AAD Connection required"
        } else {
            #get tenant name for AAD Connection
            $tname= (Get-MsolDomain | ?{ $_.IsInitial -eq $true}).Name.Split(".")[0]
        }

        #Imports SharePoint Online session commands into your local Windows PowerShell session.
        Import-Module Microsoft.Online.Sharepoint.PowerShell -DisableNameChecking -Verbose:$false
        #lookup the tenant name based on the intial domain for the tenant
        Connect-SPOService -url https://$tname-admin.sharepoint.com -Credential $admincredentials

        try { 
            write-verbose "Connecting to SharePoint Online PNP"
            import-Module OfficeDevPnP.PowerShell.V16.Commands -DisableNameChecking -Verbose:$false
            Connect-SPOnline -Credential $admincredentials -url "https://$tname.sharepoint.com"
        } catch {
            Write-Warning "Unable to connecto to SharePoint Online using the PNP PowerShell module"
        }
    }


    if ($Exchange ) {
        write-verbose "Connecting to Exchange Online"

        #Remove prior Session
        Get-PSSession -Name "Exchange Online" -ea SilentlyContinue| Remove-PSSession 

        #Creates an Exchange Online session using defined credential.
        $ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $admincredentials -Authentication "Basic" -AllowRedirection
        $ExchangeSession.Name = "Exchange Online"
        #This imports the Office 365 session into your active Shell.
        Import-PSSession $ExchangeSession -AllowClobber -Verbose:$false

    }

    if ($Compliance) {
        write-verbose "Connecting to the Unified Compliance Center"
        #Remove prior Session
        Get-PSSession -Name "Compliance Center" -ea SilentlyContinue| Remove-PSSession 

        $PSCompliance = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid/ -Credential $AdminCredentials -Authentication Basic -AllowRedirection
        $PSCompliance.Name = "Compliance Center"
        Import-PSSession $PSCompliance -AllowClobber -Verbose:$false 

    }


    If ($AADRM) {
        write-verbose "Connecting to Azure Rights Management"    
        #Azure RMS

        import-module AADRM -Verbose:$false
        Connect-AadrmService -Credential $admincredentials 
    }

}



if ($install) {

# Get the location of the downloads folder
# Ref : http://stackoverflow.com/questions/25049875/getting-any-special-folder-path-in-powershell-using-folder-guid
Add-Type @"
    using System;
    using System.Runtime.InteropServices;
 
    public static class KnownFolder
    {
        public static readonly Guid Documents = new Guid( "FDD39AD0-238F-46AF-ADB4-6C85480369C7" );
        public static readonly Guid Downloads = new Guid( "374DE290-123F-4565-9164-39C4925E467B" );
    }
    public class shell32
    {
        [DllImport("shell32.dll")]
        private static extern int SHGetKnownFolderPath(
                [MarshalAs(UnmanagedType.LPStruct)]
                Guid rfid,
                uint dwFlags,
                IntPtr hToken,
                out IntPtr pszPath
            );
            public static string GetKnownFolderPath(Guid rfid)
            {
            IntPtr pszPath;
            if (SHGetKnownFolderPath(rfid, 0, IntPtr.Zero, out pszPath) != 0)
                return ""; // add whatever error handling you fancy
            string path = Marshal.PtrToStringUni(pszPath);
            Marshal.FreeCoTaskMem(pszPath);
            return path;
            }
    }
"@
 
    #Lookup downloads location
    if ($Folder -eq $null ) {$folder = [shell32]::GetKnownFolderPath([KnownFolder]::Downloads) }
    write-verbose "Download folder : $folder"

    $Components = ConvertFrom-Json @"
    {
      "AdminComponents": [
        {
            "Tag": "SIA",
            "Name": "Microsoft Online Services Sign-In Assistant for IT Professionals",
            "Source": "http://download.microsoft.com/download/5/0/1/5017D39B-8E29-48C8-91A8-8D0E4968E6D4/$Language/msoidcli_64.msi",
            "Type": "MSI",
            "ID": "{D8AB93B0-6FBF-44A0-971F-C0669B5AE6DD}"
        } ,
        {
            "Tag" : "AAD",
            "Module" : "MSOnline",
            "Name": "Windows Azure Active Directory Module for Windows PowerShell",
            "Source": "https://bposast.vo.msecnd.net/MSOPMW/Current/amd64/AdministrationConfig-$Language.msi",
            "Type": "MSI",
            "ID": "{43CC9C53-A217-4850-B5B2-8C347920E500}"
        },
        {
            "Tag": "SKYPE",
            "Module": "SkypeOnlineConnector",
            "Name": "Skype for Business Online, Windows PowerShell Module",
            "Source": "https://download.microsoft.com/download/2/0/5/2050B39B-4DA5-48E0-B768-583533B42C3B/SkypeOnlinePowershell.exe",
            "Type": "EXE",
            "SetupOptions" : "/Install /Passive",
            "ID": "{D7334D5D-0FA2-4DA9-8D8A-883F8C0BD41B}"
        },
        {
            "Tag": "SPO",
            "Module" : "Microsoft.Online.SharePoint.PowerShell",
            "Name": "SharePoint Online Management Shell",
            "Source" : "https://download.microsoft.com/download/0/2/E/02E7E5BA-2190-44A8-B407-BC73CA0D6B87/sharepointonlinemanagementshell_4915-1200_x64_$LangCountry.msi",
            "Type": "MSI",
            "Version" : "16.0.4915.1200",
            "Web": "https://www.microsoft.com/en-us/download/confirmation.aspx?id=35588&6B49FDFB-8E5B-4B07-BC31-15695C5A2143=1",
            "ID": "{95160000-115B-0409-1000-0000000FF1CE}"
        },
        {
            "Tag": "RMS",
            "Module" : "aadrm",
            "Name": "Windows Azure AD Rights Management Administration",
            "Source" : "https://download.microsoft.com/download/1/6/6/166A2668-2FA6-4C8C-BBC5-93409D47B339/WindowsAzureADRightsManagementAdministration_x64.exe",
            "Type": "EXE",
            "Version" : " 1.0.1443.901",
            "Web": "https://www.microsoft.com/en-us/download/confirmation.aspx?id=30339",
            "ID": "{6EACEC8B-7174-4180-B8D6-528D7B2C09F0}"
        },
        {
            "Tag": "PNPPS",
            "Preview" : "Yes",
            "Module": "OfficeDevPnP.PowerShell.V16.Commands",
            "Name": "OfficeDevPnP.PowerShell",
            "Source" : "PSGallery",
            "Type": "Module",
            "Web": "https://github.com/OfficeDev/PnP-PowerShell"
        }
        
        ],
        "Exclude" : [
        {
            "Tag": "WMF5-Preview",
            "Preview" : "Yes",
            "Module" : "WMF5",
            "Type": "MSI",
            "Name": "Windows Management Framework 5.0 Production Preview",
            "Source" : "https://download.microsoft.com/download/3/F/D/3FD04B49-26F9-4D9A-8C34-4533B9D5B020/Win8.1AndW2K12R2-KB3066437-x64.msu",
            "SetupOptions" : "/quiet",
            "OS": "6,8",
            "XVersion" : "",
            "Web": "https://www.microsoft.com/en-us/download/confirmation.aspx?id=30339",
            "ID": "{BE4B4004-DE97-4185-A2B4-C147DAC9AD2C}",
            "Source2" : "https://download.microsoft.com/download/3/F/D/3FD04B49-26F9-4D9A-8C34-4533B9D5B020/W2K12-KB3066438-x64.msu"
        }

      ]
    }
"@



    # (Get-Module aadrm -ListAvailable).Version
    <# use the below in order to update relevant information
    Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | sort -Property DisplayName | select PSChildName, DisplayName, Publisher, DisplayVersion
    #>

    foreach ($c  in $Components.AdminComponents) {
        if ($c.Preview -ieq "Yes" -and $InstallPreview -eq $false) {
                write-host -f Gray "Skip Preview component : $($c.Name)"
                continue; 
        }
        #IF OS Major Specified , and if the current OS Matches the specified OS
        if ($c.OS) { 
            if ( ($c.OS).Split(",") -notcontains $PSVersionTable.BuildVersion.Major) { 
                write-host -f Gray "OS mismatch, Skip component : $($c.Name)"
                continue; 
            } 
        }

        switch ($c.Type.ToUpper() )
        {
            {$_ -in "EXE","MSI"} {
                Write-Verbose "EXE or MSI package"
                $AppInfo = Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$($c.ID)" -ErrorAction SilentlyContinue
                $Installed = $false;
                if ($AppInfo ) { 
                    $Installed = $appInfo.Displayname -ne $null
                }
                
                if (-not $Installed -or $force) {
                    # Filename
                    $file = Split-Path $c.Source -Leaf
                    #path in downloads
                    $msi = join-path $folder $file
                    #remove existing item prior to Downloading
                    Remove-Item $msi -Force -ea SilentlyContinue
                    try { 
                        #download it
                        Write-Verbose "Download package"
                        Invoke-WebRequest $c.Source -OutFile $msi
                        if ( Test-Path $msi ) { 
                            $Sign = Get-AuthenticodeSignature $msi
                            if ( $Sign.Status -eq 'Valid' -and $sign.SignerCertificate.DnsNameList[0].Unicode -eq 'Microsoft Corporation' ) {
                                if ($force) { 
                                    #de-install before re-install
                                    write-host -ForegroundColor Yellow "Removing current $($c.Type) package"
                                    Start-Process -FilePath "msiexec" -ArgumentList "/uninstall $($c.ID) " -Wait
                                }
                                try { 
                                    if ($c.Type -ieq "MSI" ) {
                                        Write-Verbose "Install MSI package : $msi"
                                        Start-Process -FilePath "msiexec" -ArgumentList "/package $msi /passive" -Wait
                                    } else {
                                        $Options = "/Passive"
                                        if ($c.Setup ) { $Options = $c.SetupOptions }
                                        Write-Verbose "Install EXE package : $msi $options"                                        
                                        Start-Process -FilePath $MSI -ArgumentList $options -Wait
                                    }

                                        $AppInfo = Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$($c.ID)" -ErrorAction SilentlyContinue
                                        Write-Host $c.Name "Version :" $AppInfo.DisplayVersion " was installed" -f Green
                                } catch {
                                    Write-warning "$($c.Name) could not be installed"
                                    #Open in browser
                                    if (-not [string]::IsNullOrEmpty($c.Web)){
                                        Start-Process $c.Web
                                    }
                                } 
                            }
                        }
                    } catch {
                        Write-Warning "could not install: $($c.Name)"
                    }
                } 
                else { 
                    Write-Host $c.Name "Version :" $AppInfo.DisplayVersion " is already installed"
                }
            }
            "MODULE" {
                # Add check for PS5 / WMF 5
                if (Get-Command install-module) { 
                    #check for installed version of this module
                    $Current  = Get-Module $c.Module -ListAvailable
                    $Source = Find-Module -Name $c.Module -Repository $c.Source -Verbose:$false
                    if ( $Current -eq $null ) {
                        write-verbose "Preparing to install module $($c.Module)"
                        #Not yet installed , find source
                        #if not installed or newer version avaialble
                        if( $Source -and $Current -eq $null -or  ($Source.Version -GT $Current.Version) ) {
                            #install it
                            $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 
                            #Check Admin Perms
                            if (-not $IsAdmin) {
                                Write-Verbose "Start PS elevated to admin to run the install"
                                Start-Process "$psHome\powershell.exe" -Verb Runas -ArgumentList "-Command Install-Module -Name $($c.Module) -Repository $($c.Source)" 

                            } else {
                                Install-Module -InputObject $source
                            }
                            $Now = Get-Module $c.Module -ListAvailable -Verbose:$false
                            if ($now){
                                Write-Host "Installed $($now.Name) version : $($Now.Version)"
                            }
                        } else {
                            #Could not be found
                            Write-warning  "The Module $($c.Name) Could not be located"
                            #Open in browser
                            if (-not [string]::IsNullOrEmpty($c.Web)){
                                Start-Process $c.Web
                            }
                        } 
                    } else {
                        #version already installed
                        if ($Source.Version -gt $Current.Version -or $force ) {
                            write-verbose "Updating Module $($c.Module)"
                            if (-not $IsAdmin) {
                                Write-Verbose "Start PS elevated to admin to run the install"
                                Start-Process "$psHome\powershell.exe" -Verb Runas -ArgumentList "-Command update-Module -Name $($c.Module) -Force:$force" 
                            } else {
                                update-Module -InputObject $source -Force:$force
                            }
                        }
                        else 
                        {
                            Write-verbose "$($c.Name) Version : $($Current.Version) is already installed"
                        }
                        $NOW  = Get-Module $c.Module -ListAvailable
                        Write-Host $c.Name "Version :" $NOW.Version" is now installed"
                                       
                    }
                } 
                else 
                { 
                    #No PS5 / WMF 5
                    Write-warning  "The Module $($c.Name) cannot be installed automatically. Please install manually or install WMF 5 (preview)"
                    #Open in browser
                    if (-not [string]::IsNullOrEmpty($c.Web)){
                        Start-Process $c.Web
                    }
                }
            }
        default { Write-Warning "Unknown component type"}
        }
    }
}