From 38225ce419b8554252ac6d56fdb61a79f95c5c6f Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Tue, 16 May 2017 22:48:34 +0200 Subject: [PATCH 01/20] Refactor scripts and functions --- src/ConnectAzureADOnline.ps1 | 28 ++++++++++++++++++++++++++++ src/ConnectMsolServiceOnline.ps1 | 28 ++++++++++++++++++++++++++++ src/DisconnectAzureADOnline.ps1 | 14 ++++++++++++++ src/DisconnectMsolServiceOnline.ps1 | 15 +++++++++++++++ src/GetAzureADCredential.ps1 | 27 +++++++++++++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 src/ConnectAzureADOnline.ps1 create mode 100644 src/ConnectMsolServiceOnline.ps1 create mode 100644 src/DisconnectAzureADOnline.ps1 create mode 100644 src/DisconnectMsolServiceOnline.ps1 create mode 100644 src/GetAzureADCredential.ps1 diff --git a/src/ConnectAzureADOnline.ps1 b/src/ConnectAzureADOnline.ps1 new file mode 100644 index 0000000..79857f5 --- /dev/null +++ b/src/ConnectAzureADOnline.ps1 @@ -0,0 +1,28 @@ +function Connect-AzureADOnline { + [CmdletBinding()] + param () + + $module = Get-Module -Name AzureAD -ListAvailable + if ($null -eq $module) { + Write-Warning -Message "Requires the module 'AzureAD' to Connect to AzureAD" + Write-Verbose -Message 'Download from: https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD"' -Verbose + return + } + else { + try { + Import-Module -Name 'AzureAD' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to Import-Module "AzureAD" - {0}' -f $_.Exception.Message) + return + } + + try { + $null = Connect-AzureAD -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to connect to AzureAD - {0}' -f $_.Exception.Message) + return + } + } +} \ No newline at end of file diff --git a/src/ConnectMsolServiceOnline.ps1 b/src/ConnectMsolServiceOnline.ps1 new file mode 100644 index 0000000..ac4f6aa --- /dev/null +++ b/src/ConnectMsolServiceOnline.ps1 @@ -0,0 +1,28 @@ +function ConnectMsolServiceOnline { + [CmdletBinding()] + param () + + $Module = Get-Module -Name 'MSOnline' -ListAvailable + if ($null -eq $Module) { + Write-Warning -Message "Requires the module 'MSOnline' to Connect to MsolService" + Write-Verbose -Message 'Download from: http://go.microsoft.com/fwlink/?linkid=236297' -Verbose + return + } + else { + try { + Import-Module -Name 'MSOnline' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to Import-Module "MSOnline" - {0}' -f $_.Exception.Message) + return + } + + try { + Connect-MsolService -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to connect to MSOnline - {0}' -f $_.Exception.Message) + return + } + } +} \ No newline at end of file diff --git a/src/DisconnectAzureADOnline.ps1 b/src/DisconnectAzureADOnline.ps1 new file mode 100644 index 0000000..51b95d6 --- /dev/null +++ b/src/DisconnectAzureADOnline.ps1 @@ -0,0 +1,14 @@ +function DisconnectAzureADOnline { + [CmdletBinding()] + param () + + try { + Disconnect-AzureAD -ErrorAction Stop + Remove-Module -Name AzureAD -Force -ErrorAction Stop + Write-Verbose -Message 'Azure AD Session is now closed.' -Verbose + } + catch { + Write-Warning -Message ('Unable to remove AzureAD Session - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/DisconnectMsolServiceOnline.ps1 b/src/DisconnectMsolServiceOnline.ps1 new file mode 100644 index 0000000..3300194 --- /dev/null +++ b/src/DisconnectMsolServiceOnline.ps1 @@ -0,0 +1,15 @@ +function DisconnectMsolServiceOnline { + [CmdletBinding()] + param () + + try { + if (Get-Module -Name 'MSOnline') { + Remove-Module -Name 'MSOnline' -ErrorAction Stop -WarningAction SilentlyContinue + Write-Verbose -Message 'MsolService Module is now closed.' -Verbose + } + } + catch { + Write-Warning -Message ('Unable to remove MsolService Module - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/GetAzureADCredential.ps1 b/src/GetAzureADCredential.ps1 new file mode 100644 index 0000000..b315bfa --- /dev/null +++ b/src/GetAzureADCredential.ps1 @@ -0,0 +1,27 @@ +function GetAzureADCredential { + [CmdletBinding()] + param () + + try { + if ($Script:AzureADCredentials -eq $false) { + $Counter = 0 + do { + $Script:AzureADCredentials = Get-Credential -Message 'UserPrincipalName in Azure AD to access Office 365.' + if ($Counter -gt 0 -and $Counter -lt 3) { + Write-Verbose -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' -Verbose + } + elseif ($Counter -gt 2) { + Write-Error -Message 'Credentials does not match a UserPrincipalName in AzureAD' -Exception 'System.Management.Automation.SetValueException' -Category InvalidResult -ErrorAction Stop + break + } + $Counter++ + } + while ($Script:AzureADCredentials.UserName -notmatch "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") + } + } + catch { + return + } + + return +} \ No newline at end of file From 1dac51225974f73be792d8bb2d6e6447d321125e Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Wed, 17 May 2017 12:20:15 +0200 Subject: [PATCH 02/20] More restructure of code --- src/ConnectCCOnline.ps1 | 25 ++++++++++++++++++++ src/ConnectExchangeOnline.ps1 | 25 ++++++++++++++++++++ src/ConnectSPOnline.ps1 | 35 ++++++++++++++++++++++++++++ src/ConnectSfBOnline.ps1 | 39 ++++++++++++++++++++++++++++++++ src/DisconnectCCOnline.ps1 | 15 ++++++++++++ src/DisconnectExchangeOnline.ps1 | 15 ++++++++++++ src/DisconnectSPOnline.ps1 | 16 +++++++++++++ src/GetCCOnlineSession.ps1 | 14 ++++++++++++ src/GetExchangeOnlineSession.ps1 | 16 +++++++++++++ 9 files changed, 200 insertions(+) create mode 100644 src/ConnectCCOnline.ps1 create mode 100644 src/ConnectExchangeOnline.ps1 create mode 100644 src/ConnectSPOnline.ps1 create mode 100644 src/ConnectSfBOnline.ps1 create mode 100644 src/DisconnectCCOnline.ps1 create mode 100644 src/DisconnectExchangeOnline.ps1 create mode 100644 src/DisconnectSPOnline.ps1 create mode 100644 src/GetCCOnlineSession.ps1 create mode 100644 src/GetExchangeOnlineSession.ps1 diff --git a/src/ConnectCCOnline.ps1 b/src/ConnectCCOnline.ps1 new file mode 100644 index 0000000..12ee8ef --- /dev/null +++ b/src/ConnectCCOnline.ps1 @@ -0,0 +1,25 @@ +function ConnectCCOnline { + [CmdletBinding()] + param () + + if ($null -ne (Get-CCOnlineSession)) { + if (Get-Command -Name 'Get-ComplianceSearch') { + Write-Verbose -Message 'Compliance Center PowerShell session already existis.' -Verbose + return + } + } + try { + $null = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' -Credential $Script:AzureADCredentials -Authentication Basic -AllowRedirection -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to create PSSession to Compliance Center - {0}' -f $_.Exception.Message) + return + } + try { + $null = Import-Module (Import-PSSession -Session (Get-CCOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) -DisableNameChecking -Global -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to load PSSession for Compliance Center - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/ConnectExchangeOnline.ps1 b/src/ConnectExchangeOnline.ps1 new file mode 100644 index 0000000..e2829cf --- /dev/null +++ b/src/ConnectExchangeOnline.ps1 @@ -0,0 +1,25 @@ +function ConnectExchangeOnline { + [CmdletBinding()] + param () + + if ($null -ne (Get-ExchangeOnlineSession)) { + if (Get-Command -Name 'Get-Mailbox') { + Write-Verbose -Message 'Exchange Online PowerShell session already existis.' -Verbose + return + } + } + try { + $null = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' -Credential $Script:AzureADCredentials -Authentication Basic -AllowRedirection -WarningAction SilentlyContinue -ErrorAction Stop + } + catch { + Write-Warning -Message ('Unable to create PSSession to Exchange Online - {0}' -f $_.Exception.Message) + return + } + try { + $null = Import-Module (Import-PSSession -Session (Get-ExchangeOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) -DisableNameChecking -Global -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to load PSSession for Exchange Online - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/ConnectSPOnline.ps1 b/src/ConnectSPOnline.ps1 new file mode 100644 index 0000000..795ea59 --- /dev/null +++ b/src/ConnectSPOnline.ps1 @@ -0,0 +1,35 @@ +function ConnectSPOnline { + [CmdletBinding()] + param ( + [Parameter( + Mandatory = $true, + HelpMessage = 'Enter a valid Sharepoint Online Domain. Example: "Contoso"' + )] + [Alias('Domain','DomainHost','Customer')] + [string]$SharepointDomain + ) + + $Module = Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ListAvailable + if ($null -eq $Module) { + Write-Warning -Message "Requires the module 'Microsoft.Online.SharePoint.PowerShell' for connection to Sharepoint Online" + Write-Verbose -Message 'Download from: https://www.microsoft.com/en-us/download/details.aspx?id=35588' -Verbose + return + } + else { + try { + Import-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to Import-Module "Microsoft.Online.SharePoint.PowerShell" - {0}' -f $_.Exception.Message) + return + } + + try { + Connect-SPOService -Url ('https://{0}-admin.sharepoint.com' -f ($SharepointDomain)) -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to Connect to Sharepoint Online Session - {0}' -f $_.Exception.Message) + return + } + } +} \ No newline at end of file diff --git a/src/ConnectSfBOnline.ps1 b/src/ConnectSfBOnline.ps1 new file mode 100644 index 0000000..24bfca6 --- /dev/null +++ b/src/ConnectSfBOnline.ps1 @@ -0,0 +1,39 @@ +function ConnectSfBOnline { + [CmdletBinding()] + param () + + $Module = Get-Module -Name 'SkypeOnlineConnector' -ListAvailable + if ($null -eq $Module) { + Write-Warning -Message "Requires the module 'SkypeOnlineConnector'" + Write-Verbose -Message 'Download from: https://www.microsoft.com/en-us/download/details.aspx?id=39366' -Verbose + return + } + else { + if ($null -ne (Get-SfBOnlineSession)) { + Write-Verbose -Message 'Skype for Business Online PowerShell PSSession already existis.' + return + } + try { + Import-Module -Name 'SkypeOnlineConnector' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to Import-Module "LyncOnlineConnector" - {0}' -f $_.Exception.Message) + return + } + + try { + $null = New-CsOnlineSession -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to create PSSession for Skype for Business Online - {0}' -f $_.Exception.Message) + return + } + try { + $null = Import-PSSession -Session (Get-SfBOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + Write-Warning -Message ('Unable to load PSSession for Skype for Business Online - {0}' -f $_.Exception.Message) + return + } + } +} \ No newline at end of file diff --git a/src/DisconnectCCOnline.ps1 b/src/DisconnectCCOnline.ps1 new file mode 100644 index 0000000..44876b0 --- /dev/null +++ b/src/DisconnectCCOnline.ps1 @@ -0,0 +1,15 @@ +function DisconnectCCOnline { + [CmdletBinding()] + param () + + try { + if ($null -ne ($ccsession = Get-CCOnlineSession)) { + Remove-PSSession -Session ($ccsession) -ErrorAction Stop + Write-Verbose -Message 'The Compliance Center Online PSSession is now closed.' -Verbose + } + } + catch { + Write-Warning -Message ('Unable to remove PSSession for Compliance Center - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/DisconnectExchangeOnline.ps1 b/src/DisconnectExchangeOnline.ps1 new file mode 100644 index 0000000..56e22d3 --- /dev/null +++ b/src/DisconnectExchangeOnline.ps1 @@ -0,0 +1,15 @@ +function DisconnectExchangeOnline { + [CmdletBinding()] + param () + + try { + if ($null -ne ($exonline = Get-ExchangeOnlineSession)) { + Remove-PSSession -Session ($exonline) -ErrorAction Stop + Write-Verbose -Message 'The Exchange Online PSSession is now closed.' -Verbose + } + } + catch { + Write-Warning -Message ('Unable to remove PSSession for Exchange Online - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/DisconnectSPOnline.ps1 b/src/DisconnectSPOnline.ps1 new file mode 100644 index 0000000..0edcaed --- /dev/null +++ b/src/DisconnectSPOnline.ps1 @@ -0,0 +1,16 @@ +function DisconnectSPOnline { + [CmdletBinding()] + param () + + try { + $null = Disconnect-SPOService -ErrorAction Stop + Remove-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -Force -ErrorAction Stop + Write-Verbose -Message 'The Sharepoint Online Session is now closed.' -Verbose + } + catch { + if ($_.Exception.Message -notmatch 'There is no service currently connected') { + Write-Warning -Message ('Unable to disconnect Sharepoint Online Session - {0}' -f $_.Exception.Message) + return + } + } +} \ No newline at end of file diff --git a/src/GetCCOnlineSession.ps1 b/src/GetCCOnlineSession.ps1 new file mode 100644 index 0000000..582662c --- /dev/null +++ b/src/GetCCOnlineSession.ps1 @@ -0,0 +1,14 @@ +function GetCCOnlineSession { + [CmdletBinding()] + param () + + try { + $session = Get-PSSession -ErrorAction Stop | Where-Object -FilterScript {$_.ComputerName -match 'Compliance' -and $_.ConfigurationName -eq 'Microsoft.Exchange'} + } + catch { + Write-Warning -Message ('Unable to get active Compliance Center Online PSSession - {0}' -f $_.Exception.Message) + return $null + } + + return $session +} \ No newline at end of file diff --git a/src/GetExchangeOnlineSession.ps1 b/src/GetExchangeOnlineSession.ps1 new file mode 100644 index 0000000..33d1872 --- /dev/null +++ b/src/GetExchangeOnlineSession.ps1 @@ -0,0 +1,16 @@ +function GetExchangeOnlineSession { + [CmdletBinding()] + param () + + try { + $session = Get-PSSession -ErrorAction Stop | Where-Object -FilterScript { + $_.ComputerName -match 'outlook.office365.com' -and $_.ConfigurationName -eq 'Microsoft.Exchange' + } + } + catch { + Write-Warning -Message ('Unable to get active Exchange Online PSSession - {0}' -f $_.Exception.Message) + return $null + } + + return $session +} \ No newline at end of file From 1dcd026876f0cf04dda7efc996ec3baff4edd8bc Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Wed, 17 May 2017 13:23:03 +0200 Subject: [PATCH 03/20] More restructure of code --- src/Connect-Office365.ps1 | 181 +++++++++++++++++++++++++++++++++++ src/Disconnect-Office365.ps1 | 119 +++++++++++++++++++++++ src/DisconnectSfBOnline.ps1 | 16 ++++ src/GetSfBOnlineSession.ps1 | 15 +++ 4 files changed, 331 insertions(+) create mode 100644 src/Connect-Office365.ps1 create mode 100644 src/Disconnect-Office365.ps1 create mode 100644 src/DisconnectSfBOnline.ps1 create mode 100644 src/GetSfBOnlineSession.ps1 diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 new file mode 100644 index 0000000..d59a216 --- /dev/null +++ b/src/Connect-Office365.ps1 @@ -0,0 +1,181 @@ +function Connect-Office365 { + <# + .SYNOPSIS + Connect to one or more Office 365 services using Powershell. + + .DESCRIPTION + Connect to one or more Office 365 (AzureAD) services using Powershell. Some services requires the installation of separate PowerShell modules or binaries. + AzureAD requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD" + MsolService requires a separate module - http://go.microsoft.com/fwlink/?linkid=236297 + Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 + Skype for Business Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 + + DYNAMIC PARAMETERS + -SharepointDomain + Parameter available when the Service parameter contains AllService or SharepointOnline. + The SharepointDomain parameter is necessary when connecting to Sharepoint Online sessions ('https://{0}-admin.sharepoint.com' -f $SharepointDomain). + Example for SharepointDomain can be 'Contoso'. + + .EXAMPLE + Connect-Office365 + + + VERBOSE: Conncting to AzureAD. + VERBOSE: Conncting to MSolService. + + This command connects to AzureAD and MsolService service sessions using the credentials provided when prompted. + AzureAD and MsolService are the default parameter values for the parameter Services. + + .EXAMPLE + Connect-Office365 -Service ComplianceCenter, ExchangeOnline, AzureAD + + + VERBOSE: Conncting to AzureAD. + VERBOSE: Conncting to Compliance Center. + VERBOSE: Conncting to Exchange Online. + + This command connects to AzureAD, ComplianceCenter and ExchageOnline service sessions using the credentials provided when prompted. + + .EXAMPLE + Connect-Office365 -Service AzureAD, SharepointOnline + + + cmdlet Connect-Office365 at command pipeline position 1 + Supply values for the following parameters: + (Type !? for Help.) + SharepointDomain: Contoso + + VERBOSE: Conncting to AzureAD. + VERBOSE: Conncting to Sharepoint Online. + + This command connect to AzureAD and SharepointOnline. + SharepointOnline session requires a specified URI when connecting. In this example, Contoso, is provided at the mandatory prompt parameter, SharepointDomain. + + .EXAMPLE + Connect-Office365 -Service AllServices -SharepointDomain Contoso + + + VERBOSE: Connecting to all Office 365 Services. + + This command connects to all Office 365 service sessions using the credentials provided when prompted. + The parameter SharepointDomain is explicit provided to avoid the mandatory parameter prompt. + + + .NOTES + Created on: 2017-02-23 14:56 + Created by: Philip Haglund + Organization: Gonjer.com + Version: 1.5.0 + Requirements: Powershell 3.0 + + .LINK + https://github.com/PhilipHaglund/Office365Connect/ + https://gonjer.com/ + #> + [CmdletBinding( + SupportsShouldProcess = $true + )] + param( + # Provide one or more Office 365 services to connect to. + # Valid values are: + # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' + [Parameter( + ValueFromPipeline = $true, + Position = 0 + )] + [ValidateSet('AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline')] + [ValidateNotNullOrEmpty()] + [string[]]$Service = @('AzureAD','MSOnline') + ) + + dynamicparam { + + if ($Service -match 'AllServices|SharepointOnline') { + + # Create a ParameterAttribute Object + $SPAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute + $SPAttrib.Position = 1 + $SPAttrib.Mandatory = $true + $SPAttrib.HelpMessage = 'Enter a valid Sharepoint Online Domain. Example: "Contoso"' + + # Create an AliasAttribute Object for the parameter + $SPAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList @('Domain','DomainHost','Customer') + + # Create an AttributeCollection Object + $SPCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] + + # Add the attributes to the AttributeCollection + $SPCollection.Add($SPAttrib) + $SPCollection.Add($SPAlias) + + # Add the SharepointDomain paramater to the "Runtime" + $SPParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList ('SharepointDomain', [string], $SPCollection) + + # Expose the parameter + $SPParamDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary + $SPParamDictionary.Add('SharepointDomain', $SPParam) + return $SPParamDictionary + } + } + begin { + + if (($service = $Service | Sort-Object -Unique).Count -gt 5 -or $Service -eq 'All') { + $Service = 'AllServices' + } + } + process { + + foreach ($s in $Service) { + + if ($PSCmdlet.ShouldProcess('Establishing a PowerShell session to {0} - Office 365.' -f ('{0}' -f $s), $MyInvocation.MyCommand.Name)) { + $null = Get-AzureADCredential + + if ($Script:AzureADCredentials -eq $false) { + Write-Warning -Message 'Need valid credentials to connect, please provide the correct credentials.' + break + } + + switch ($s) { + + 'AzureAD' { + Write-Verbose -Message 'Conncting to AzureAD.' -Verbose + Connect-AzureADOnline + } + 'MSOnline' { + Write-Verbose -Message 'Conncting to MSolService.' -Verbose + Connect-MsolServiceOnline + } + 'ComplianceCenter' { + Write-Verbose -Message 'Conncting to Compliance Center.' -Verbose + Connect-CCOnline + } + 'ExchangeOnline' { + Write-Verbose -Message 'Conncting to Exchange Online.' -Verbose + Connect-ExchangeOnline + } + 'SharepointOnline' { + Write-Verbose -Message 'Conncting to Sharepoint Online.' -Verbose + Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] + } + 'SkypeforBusinessOnline' { + Write-Verbose -Message 'Conncting to Skype for Business Online.' -Verbose + Connect-SfBOnline + } + Default { + Write-Verbose -Message 'Connecting to all Office 365 Services.' -Verbose + Connect-AzureADOnline + Connect-MsolServiceOnline + Connect-CCOnline + Connect-ExchangeOnline + Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] + Connect-SfBOnline + } + } + } + } + } + end { + + Set-Variable -Name AzureADCredentials -Scope Script -Value $null -ErrorAction SilentlyContinue + } +} \ No newline at end of file diff --git a/src/Disconnect-Office365.ps1 b/src/Disconnect-Office365.ps1 new file mode 100644 index 0000000..71973cf --- /dev/null +++ b/src/Disconnect-Office365.ps1 @@ -0,0 +1,119 @@ +function Disconnect-Office365 { + <# + .SYNOPSIS + Disconnect from one or more Office 365 services using Powershell. + + .DESCRIPTION + Disconnect from one or more Office 365 (AzureAD) services using Powershell. Some services requires the installation of separate PowerShell modules or binaires. + AzureAD requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD" + MsolService requraes a seprate module - http://go.microsoft.com/fwlink/?linkid=236297 + Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 + Skype for Business Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 + + .EXAMPLE + Disconnect-Office365 + + + VERBOSE: Disconnecting from all Office 365 Services. + VERBOSE: Azure AD Session is now closed. + VERBOSE: MsolService Module is now closed. + VERBOSE: The Compliance Center Online PSSession is now closed. + VERBOSE: The Exchange Online PSSession is now closed. + VERBOSE: The Sharepoint Online Session is now closed. + VERBOSE: The Skype for Business Online PSSession is now closed. + + This command disconnects from all Office 365 service sessions that are available and running. + + .EXAMPLE + Disconnect-Office365 -Service ComplianceCenter, ExchangeOnline, AzureAD + + + VERBOSE: Disconnecting from AzureAD. + VERBOSE: Disconnecting from Compliance Center. + VERBOSE: Disconnecting from Exchange Online. + + This command disconnects from AzureAD, Compliance and Exchange Online service sessions that are available and running. + + .NOTES + Created on: 2017-02-23 14:56 + Created by: Philip Haglund + Organization: Gonjer.com + Version: 1.5.0 + Requirements: Powershell 3.0 + + .LINK + https://github.com/PhilipHaglund/Office365Connect/ + https://gonjer.com/ + #> + [CmdletBinding( + SupportsShouldProcess = $true + )] + param( + # Provide one or more Office 365 services to disconnect from. + # Valid values are: + # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline', 'SkypeforBusinessOnline' + [Parameter( + ValueFromPipeline = $true + )] + [ValidateSet('AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline')] + [ValidateNotNullOrEmpty()] + [string[]]$Service = @('AllServices') + ) + begin { + + if (($service = $Service | Sort-Object -Unique).Count -gt 5) { + $Service = 'AllServices' + } + } + process { + + foreach ($s in $Service) { + + if ($PSCmdlet.ShouldProcess('End the PowerShell session for {0} - Office 365.' -f ('{0}' -f $s), $MyInvocation.MyCommand.Name)) { + + switch ($s) { + + 'AzureAD' { + Write-Verbose -Message 'Disconnecting from AzureAD.' -Verbose + Disconnect-AzureADOnline + } + 'MSOnline' { + Write-Verbose -Message 'Disconnecting from MsolService.' -Verbose + Disconnect-MsolServiceOnline + } + 'ComplianceCenter' { + Write-Verbose -Message 'Disconnecting from Compliance Center.' -Verbose + Disconnect-CCOnline + } + 'ExchangeOnline' { + Write-Verbose -Message 'Disconnecting from Exchange Online.' -Verbose + Disconnect-ExchangeOnline + } + 'SharepointOnline' { + Write-Verbose -Message 'Disconnecting from Sharepoint Online.' -Verbose + Disconnect-SPOnline + } + 'SkypeforBusinessOnline' { + Write-Verbose -Message 'Disconnecting from Skype for Business Online.' -Verbose + Disconnect-SfBOnline + } + Default { + Write-Verbose -Message 'Disconnecting from all Office 365 Services.' -Verbose + Disconnect-AzureADOnline + Disconnect-MsolServiceOnline + Disconnect-CCOnline + Disconnect-ExchangeOnline + Disconnect-SPOnline + Disconnect-SfBOnline + } + } + + } + } + } + end { + + # If the saved credentials variables for some reason is not removed we remove them again. + Set-Variable -Name AzureADCredentials -Scope Script -Value $null -ErrorAction SilentlyContinue + } +} \ No newline at end of file diff --git a/src/DisconnectSfBOnline.ps1 b/src/DisconnectSfBOnline.ps1 new file mode 100644 index 0000000..ec88c75 --- /dev/null +++ b/src/DisconnectSfBOnline.ps1 @@ -0,0 +1,16 @@ +function DisconnectSfBOnline { + [CmdletBinding()] + param () + + try { + if ($null -ne ($SbfoSession = Get-SfBOnlineSession)) { + Remove-PSSession -Session ($SbfoSession) -ErrorAction Stop + Remove-Module -Name 'SkypeOnlineConnector' -Force -ErrorAction Stop + Write-Verbose -Message 'The Skype for Business Online PSSession is now closed.' -Verbose + } + } + catch { + Write-Warning -Message ('Unable to remove PSSession for Skype for Business Online - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/GetSfBOnlineSession.ps1 b/src/GetSfBOnlineSession.ps1 new file mode 100644 index 0000000..5386428 --- /dev/null +++ b/src/GetSfBOnlineSession.ps1 @@ -0,0 +1,15 @@ +function GetSfBOnlineSession { + [CmdletBinding()] + param () + + try { + $Session = Get-PSSession -ErrorAction Stop | Where-Object -FilterScript { + $_.ComputerName -match 'online.lync.com' -and $_.ConfigurationName -eq 'Microsoft.PowerShell' + } + $Session + } + catch { + Write-Warning -Message ('Unable to get active Exchange Online PSSession - {0}' -f $_.Exception.Message) + return $null + } +} \ No newline at end of file From fd78fdc109cc3c202e94f398cd00ec5a219b52da Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Wed, 17 May 2017 16:34:35 +0200 Subject: [PATCH 04/20] Refactor and restrcture of code --- ...ADOnline.ps1 => Connect-AzureADOnline.ps1} | 21 +++-- src/Connect-CCOnline.ps1 | 43 ++++++++++ src/Connect-ExchangeOnline.ps1 | 38 +++++++++ src/Connect-ExchangeOnlineProt.ps1 | 38 +++++++++ ...line.ps1 => Connect-MsolServiceOnline.ps1} | 17 +++- src/Connect-Office365.ps1 | 13 ++- ...nnectSPOnline.ps1 => Connect-SPOnline.ps1} | 79 +++++++++++-------- ...ectSfBOnline.ps1 => Connect-SfBOnline.ps1} | 23 +++++- src/ConnectCCOnline.ps1 | 25 ------ src/ConnectExchangeOnline.ps1 | 25 ------ ...nline.ps1 => Disconnect-AzureADOnline.ps1} | 7 +- ...ctCCOnline.ps1 => Disconnect-CCOnline.ps1} | 12 ++- ...line.ps1 => Disconnect-ExchangeOnline.ps1} | 12 ++- src/Disconnect-ExchangeOnlineProt.ps1 | 21 +++++ ...e.ps1 => Disconnect-MsolServiceOnline.ps1} | 6 +- src/Disconnect-Office365.ps1 | 18 ++++- ...ctSPOnline.ps1 => Disconnect-SPOnline.ps1} | 6 +- ...SfBOnline.ps1 => Disconnect-SfBOnline.ps1} | 12 ++- src/Get-AzureADCredential.ps1 | 41 ++++++++++ src/Get-CCOnlineSession.ps1 | 15 ++++ src/Get-ExchangeOnlineProtSession.ps1 | 15 ++++ src/Get-ExchangeOnlineSession.ps1 | 15 ++++ src/Get-OnlinePSSession.ps1 | 32 ++++++++ src/Get-SfBOnlineSession.ps1 | 15 ++++ src/GetAzureADCredential.ps1 | 27 ------- src/GetCCOnlineSession.ps1 | 14 ---- src/GetExchangeOnlineSession.ps1 | 16 ---- src/GetSfBOnlineSession.ps1 | 15 ---- 28 files changed, 433 insertions(+), 188 deletions(-) rename src/{ConnectAzureADOnline.ps1 => Connect-AzureADOnline.ps1} (68%) create mode 100644 src/Connect-CCOnline.ps1 create mode 100644 src/Connect-ExchangeOnline.ps1 create mode 100644 src/Connect-ExchangeOnlineProt.ps1 rename src/{ConnectMsolServiceOnline.ps1 => Connect-MsolServiceOnline.ps1} (73%) rename src/{ConnectSPOnline.ps1 => Connect-SPOnline.ps1} (69%) rename src/{ConnectSfBOnline.ps1 => Connect-SfBOnline.ps1} (77%) delete mode 100644 src/ConnectCCOnline.ps1 delete mode 100644 src/ConnectExchangeOnline.ps1 rename src/{DisconnectAzureADOnline.ps1 => Disconnect-AzureADOnline.ps1} (86%) rename src/{DisconnectCCOnline.ps1 => Disconnect-CCOnline.ps1} (62%) rename src/{DisconnectExchangeOnline.ps1 => Disconnect-ExchangeOnline.ps1} (61%) create mode 100644 src/Disconnect-ExchangeOnlineProt.ps1 rename src/{DisconnectMsolServiceOnline.ps1 => Disconnect-MsolServiceOnline.ps1} (89%) rename src/{DisconnectSPOnline.ps1 => Disconnect-SPOnline.ps1} (93%) rename src/{DisconnectSfBOnline.ps1 => Disconnect-SfBOnline.ps1} (67%) create mode 100644 src/Get-AzureADCredential.ps1 create mode 100644 src/Get-CCOnlineSession.ps1 create mode 100644 src/Get-ExchangeOnlineProtSession.ps1 create mode 100644 src/Get-ExchangeOnlineSession.ps1 create mode 100644 src/Get-OnlinePSSession.ps1 create mode 100644 src/Get-SfBOnlineSession.ps1 delete mode 100644 src/GetAzureADCredential.ps1 delete mode 100644 src/GetCCOnlineSession.ps1 delete mode 100644 src/GetExchangeOnlineSession.ps1 delete mode 100644 src/GetSfBOnlineSession.ps1 diff --git a/src/ConnectAzureADOnline.ps1 b/src/Connect-AzureADOnline.ps1 similarity index 68% rename from src/ConnectAzureADOnline.ps1 rename to src/Connect-AzureADOnline.ps1 index 79857f5..1f26e2f 100644 --- a/src/ConnectAzureADOnline.ps1 +++ b/src/Connect-AzureADOnline.ps1 @@ -1,26 +1,37 @@ function Connect-AzureADOnline { + [CmdletBinding()] - param () + param( + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) + + $Module = Get-Module -Name AzureAD -ListAvailable + + if ($null -eq $Module) { - $module = Get-Module -Name AzureAD -ListAvailable - if ($null -eq $module) { Write-Warning -Message "Requires the module 'AzureAD' to Connect to AzureAD" Write-Verbose -Message 'Download from: https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD"' -Verbose return } else { + try { + Import-Module -Name 'AzureAD' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to Import-Module "AzureAD" - {0}' -f $_.Exception.Message) return } - try { - $null = Connect-AzureAD -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + try { + + $null = Connect-AzureAD -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to connect to AzureAD - {0}' -f $_.Exception.Message) return } diff --git a/src/Connect-CCOnline.ps1 b/src/Connect-CCOnline.ps1 new file mode 100644 index 0000000..ac8c8a7 --- /dev/null +++ b/src/Connect-CCOnline.ps1 @@ -0,0 +1,43 @@ +function Connect-CCOnline { + + [CmdletBinding()] + param( + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) + + if ($null -ne (Get-CCOnlineSession)) { + + if (Get-Command -Name 'Get-ComplianceSearch') { + + Write-Verbose -Message 'Compliance Center PowerShell session already existis.' -Verbose + Write-Verbose -Message 'Disconnect from the current session to start a new one.' + return + } + } + + try { + + $null = New-PSSession -ConfigurationName 'Microsoft.Exchange' ` -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` + -AllowRedirection:$true ` -ErrorAction Stop ` + -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to create PSSession to Compliance Center - {0}' -f $_.Exception.Message) + return + } + + try { + + $null = Import-Module ` (Import-PSSession -Session (Get-CCOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) ` + -DisableNameChecking ` + -Global ` + -ErrorAction Stop ` -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to load PSSession for Compliance Center - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/Connect-ExchangeOnline.ps1 b/src/Connect-ExchangeOnline.ps1 new file mode 100644 index 0000000..dc74d66 --- /dev/null +++ b/src/Connect-ExchangeOnline.ps1 @@ -0,0 +1,38 @@ +function Connect-ExchangeOnline { + + [CmdletBinding()] + param( + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) + + if ($null -ne (Get-ExchangeOnlineSession)) { + + if (Get-Command -Name 'Get-Mailbox') { + + Write-Verbose -Message 'Exchange Online PowerShell session already existis.' -Verbose + Write-Verbose -Message 'Disconnect from the current session to start a new one.' + return + } + } + + try { + + $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to create PSSession to Exchange Online - {0}' -f $_.Exception.Message) + return + } + + try { + + $null = Import-Module ` (Import-PSSession -Session (Get-ExchangeOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) ` -DisableNameChecking ` -Global ` -ErrorAction Stop ` -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to load PSSession for Exchange Online - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/Connect-ExchangeOnlineProt.ps1 b/src/Connect-ExchangeOnlineProt.ps1 new file mode 100644 index 0000000..b869fa5 --- /dev/null +++ b/src/Connect-ExchangeOnlineProt.ps1 @@ -0,0 +1,38 @@ +function Connect-ExchangeOnlineProt { + + [CmdletBinding()] + param( + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) + + if ($null -ne (Get-ExchangeOnlineProtSession)) { + + if (Get-Command -Name 'Set-EOPUser') { + + Write-Verbose -Message 'Exchange Online Protection PowerShell session already existis.' -Verbose + Write-Verbose -Message 'Disconnect from the current session to start a new one.' + return + } + } + + try { + + $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -ConnectionUri 'https://ps.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to create PSSession to Exchange Online Protection - {0}' -f $_.Exception.Message) + return + } + + try { + + $null = Import-Module ` (Import-PSSession -Session (Get-ExchangeOnlineProtSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) ` -DisableNameChecking ` -Global ` -ErrorAction Stop ` -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to load PSSession for Exchange Online Protection - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/ConnectMsolServiceOnline.ps1 b/src/Connect-MsolServiceOnline.ps1 similarity index 73% rename from src/ConnectMsolServiceOnline.ps1 rename to src/Connect-MsolServiceOnline.ps1 index ac4f6aa..5bab5e4 100644 --- a/src/ConnectMsolServiceOnline.ps1 +++ b/src/Connect-MsolServiceOnline.ps1 @@ -1,26 +1,37 @@ -function ConnectMsolServiceOnline { +function Connect-MsolServiceOnline { + [CmdletBinding()] - param () + param( + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) $Module = Get-Module -Name 'MSOnline' -ListAvailable + if ($null -eq $Module) { + Write-Warning -Message "Requires the module 'MSOnline' to Connect to MsolService" Write-Verbose -Message 'Download from: http://go.microsoft.com/fwlink/?linkid=236297' -Verbose return } else { + try { + Import-Module -Name 'MSOnline' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to Import-Module "MSOnline" - {0}' -f $_.Exception.Message) return } try { - Connect-MsolService -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + + Connect-MsolService -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to connect to MSOnline - {0}' -f $_.Exception.Message) return } diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 index d59a216..ab91bb9 100644 --- a/src/Connect-Office365.ps1 +++ b/src/Connect-Office365.ps1 @@ -9,6 +9,7 @@ MsolService requires a separate module - http://go.microsoft.com/fwlink/?linkid=236297 Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 Skype for Business Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 + Exchange Online, Exchange Online Protection, Complince Center does not require seperate binaries. DYNAMIC PARAMETERS -SharepointDomain @@ -78,12 +79,12 @@ param( # Provide one or more Office 365 services to connect to. # Valid values are: - # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' + # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' [Parameter( ValueFromPipeline = $true, Position = 0 )] - [ValidateSet('AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline')] + [ValidateSet('AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline')] [ValidateNotNullOrEmpty()] [string[]]$Service = @('AzureAD','MSOnline') ) @@ -119,7 +120,8 @@ } begin { - if (($service = $Service | Sort-Object -Unique).Count -gt 5 -or $Service -eq 'All') { + # Sorting all input strings from the Service parameter. + if (($Service = $Service | Sort-Object -Unique).Count -gt 6 -or $Service -eq 'All') { $Service = 'AllServices' } } @@ -153,6 +155,10 @@ Write-Verbose -Message 'Conncting to Exchange Online.' -Verbose Connect-ExchangeOnline } + 'ExchangeOnlineProtection' { + Write-Verbose -Message 'Conncting to Exchange Online Protection.' -Verbose + Connect-ExchangeOnlineProt + } 'SharepointOnline' { Write-Verbose -Message 'Conncting to Sharepoint Online.' -Verbose Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] @@ -167,6 +173,7 @@ Connect-MsolServiceOnline Connect-CCOnline Connect-ExchangeOnline + Connect-ExchangeOnlineProt Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] Connect-SfBOnline } diff --git a/src/ConnectSPOnline.ps1 b/src/Connect-SPOnline.ps1 similarity index 69% rename from src/ConnectSPOnline.ps1 rename to src/Connect-SPOnline.ps1 index 795ea59..d27cefc 100644 --- a/src/ConnectSPOnline.ps1 +++ b/src/Connect-SPOnline.ps1 @@ -1,35 +1,46 @@ -function ConnectSPOnline { - [CmdletBinding()] - param ( - [Parameter( - Mandatory = $true, - HelpMessage = 'Enter a valid Sharepoint Online Domain. Example: "Contoso"' - )] - [Alias('Domain','DomainHost','Customer')] - [string]$SharepointDomain - ) - - $Module = Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ListAvailable - if ($null -eq $Module) { - Write-Warning -Message "Requires the module 'Microsoft.Online.SharePoint.PowerShell' for connection to Sharepoint Online" - Write-Verbose -Message 'Download from: https://www.microsoft.com/en-us/download/details.aspx?id=35588' -Verbose - return - } - else { - try { - Import-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue - } - catch { - Write-Warning -Message ('Unable to Import-Module "Microsoft.Online.SharePoint.PowerShell" - {0}' -f $_.Exception.Message) - return - } - - try { - Connect-SPOService -Url ('https://{0}-admin.sharepoint.com' -f ($SharepointDomain)) -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue - } - catch { - Write-Warning -Message ('Unable to Connect to Sharepoint Online Session - {0}' -f $_.Exception.Message) - return - } - } +function Connect-SPOnline { + + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true, + HelpMessage = 'Enter a valid Sharepoint Online Domain. Example: "Contoso"' + )] + [Alias('Domain','DomainHost','Customer')] + [string]$SharepointDomain, + + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) + + $Module = Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ListAvailable + + if ($null -eq $Module) { + + Write-Warning -Message "Requires the module 'Microsoft.Online.SharePoint.PowerShell' for connection to Sharepoint Online" + Write-Verbose -Message 'Download from: https://www.microsoft.com/en-us/download/details.aspx?id=35588' -Verbose + return + } + else { + + try { + + Import-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to Import-Module "Microsoft.Online.SharePoint.PowerShell" - {0}' -f $_.Exception.Message) + return + } + + try { + + $null = Connect-SPOService -Url ('https://{0}-admin.sharepoint.com' -f ($SharepointDomain)) ` -Credential $Credential ` -ErrorAction Stop ` -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to Connect to Sharepoint Online Session - {0}' -f $_.Exception.Message) + return + } + } } \ No newline at end of file diff --git a/src/ConnectSfBOnline.ps1 b/src/Connect-SfBOnline.ps1 similarity index 77% rename from src/ConnectSfBOnline.ps1 rename to src/Connect-SfBOnline.ps1 index 24bfca6..8468d4b 100644 --- a/src/ConnectSfBOnline.ps1 +++ b/src/Connect-SfBOnline.ps1 @@ -1,37 +1,52 @@ -function ConnectSfBOnline { +function Connect-SfBOnline { + [CmdletBinding()] - param () + param( + [System.Management.Automation.Credential()] + [pscredential]$Credential = [pscredential]::Empty + ) $Module = Get-Module -Name 'SkypeOnlineConnector' -ListAvailable + if ($null -eq $Module) { + Write-Warning -Message "Requires the module 'SkypeOnlineConnector'" Write-Verbose -Message 'Download from: https://www.microsoft.com/en-us/download/details.aspx?id=39366' -Verbose return } else { + if ($null -ne (Get-SfBOnlineSession)) { + Write-Verbose -Message 'Skype for Business Online PowerShell PSSession already existis.' + Write-Verbose -Message 'Disconnect from the current session to start a new one.' return } try { + Import-Module -Name 'SkypeOnlineConnector' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to Import-Module "LyncOnlineConnector" - {0}' -f $_.Exception.Message) return } try { - $null = New-CsOnlineSession -Credential $Script:AzureADCredentials -ErrorAction Stop -WarningAction SilentlyContinue + + $null = New-CsOnlineSession -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to create PSSession for Skype for Business Online - {0}' -f $_.Exception.Message) return } - try { + try { + $null = Import-PSSession -Session (Get-SfBOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue } catch { + Write-Warning -Message ('Unable to load PSSession for Skype for Business Online - {0}' -f $_.Exception.Message) return } diff --git a/src/ConnectCCOnline.ps1 b/src/ConnectCCOnline.ps1 deleted file mode 100644 index 12ee8ef..0000000 --- a/src/ConnectCCOnline.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -function ConnectCCOnline { - [CmdletBinding()] - param () - - if ($null -ne (Get-CCOnlineSession)) { - if (Get-Command -Name 'Get-ComplianceSearch') { - Write-Verbose -Message 'Compliance Center PowerShell session already existis.' -Verbose - return - } - } - try { - $null = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' -Credential $Script:AzureADCredentials -Authentication Basic -AllowRedirection -ErrorAction Stop -WarningAction SilentlyContinue - } - catch { - Write-Warning -Message ('Unable to create PSSession to Compliance Center - {0}' -f $_.Exception.Message) - return - } - try { - $null = Import-Module (Import-PSSession -Session (Get-CCOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) -DisableNameChecking -Global -ErrorAction Stop -WarningAction SilentlyContinue - } - catch { - Write-Warning -Message ('Unable to load PSSession for Compliance Center - {0}' -f $_.Exception.Message) - return - } -} \ No newline at end of file diff --git a/src/ConnectExchangeOnline.ps1 b/src/ConnectExchangeOnline.ps1 deleted file mode 100644 index e2829cf..0000000 --- a/src/ConnectExchangeOnline.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -function ConnectExchangeOnline { - [CmdletBinding()] - param () - - if ($null -ne (Get-ExchangeOnlineSession)) { - if (Get-Command -Name 'Get-Mailbox') { - Write-Verbose -Message 'Exchange Online PowerShell session already existis.' -Verbose - return - } - } - try { - $null = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' -Credential $Script:AzureADCredentials -Authentication Basic -AllowRedirection -WarningAction SilentlyContinue -ErrorAction Stop - } - catch { - Write-Warning -Message ('Unable to create PSSession to Exchange Online - {0}' -f $_.Exception.Message) - return - } - try { - $null = Import-Module (Import-PSSession -Session (Get-ExchangeOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) -DisableNameChecking -Global -ErrorAction Stop -WarningAction SilentlyContinue - } - catch { - Write-Warning -Message ('Unable to load PSSession for Exchange Online - {0}' -f $_.Exception.Message) - return - } -} \ No newline at end of file diff --git a/src/DisconnectAzureADOnline.ps1 b/src/Disconnect-AzureADOnline.ps1 similarity index 86% rename from src/DisconnectAzureADOnline.ps1 rename to src/Disconnect-AzureADOnline.ps1 index 51b95d6..fd806cd 100644 --- a/src/DisconnectAzureADOnline.ps1 +++ b/src/Disconnect-AzureADOnline.ps1 @@ -1,13 +1,16 @@ -function DisconnectAzureADOnline { +function Disconnect-AzureADOnline { + [CmdletBinding()] param () - try { + try { + Disconnect-AzureAD -ErrorAction Stop Remove-Module -Name AzureAD -Force -ErrorAction Stop Write-Verbose -Message 'Azure AD Session is now closed.' -Verbose } catch { + Write-Warning -Message ('Unable to remove AzureAD Session - {0}' -f $_.Exception.Message) return } diff --git a/src/DisconnectCCOnline.ps1 b/src/Disconnect-CCOnline.ps1 similarity index 62% rename from src/DisconnectCCOnline.ps1 rename to src/Disconnect-CCOnline.ps1 index 44876b0..99cffdb 100644 --- a/src/DisconnectCCOnline.ps1 +++ b/src/Disconnect-CCOnline.ps1 @@ -1,14 +1,20 @@ -function DisconnectCCOnline { +function Disconnect-CCOnline { + [CmdletBinding()] param () try { - if ($null -ne ($ccsession = Get-CCOnlineSession)) { - Remove-PSSession -Session ($ccsession) -ErrorAction Stop + + $CCOSession = Get-CCOnlineSession + + if ($null -ne $CCOSession) { + + Remove-PSSession -Session ($CCOSession) -ErrorAction Stop Write-Verbose -Message 'The Compliance Center Online PSSession is now closed.' -Verbose } } catch { + Write-Warning -Message ('Unable to remove PSSession for Compliance Center - {0}' -f $_.Exception.Message) return } diff --git a/src/DisconnectExchangeOnline.ps1 b/src/Disconnect-ExchangeOnline.ps1 similarity index 61% rename from src/DisconnectExchangeOnline.ps1 rename to src/Disconnect-ExchangeOnline.ps1 index 56e22d3..fd9e254 100644 --- a/src/DisconnectExchangeOnline.ps1 +++ b/src/Disconnect-ExchangeOnline.ps1 @@ -1,14 +1,20 @@ -function DisconnectExchangeOnline { +function Disconnect-ExchangeOnline { + [CmdletBinding()] param () try { - if ($null -ne ($exonline = Get-ExchangeOnlineSession)) { - Remove-PSSession -Session ($exonline) -ErrorAction Stop + + $EXOnline = Get-ExchangeOnlineSession + + if ($null -ne $EXOnline) { + + Remove-PSSession -Session ($EXOnline) -ErrorAction Stop Write-Verbose -Message 'The Exchange Online PSSession is now closed.' -Verbose } } catch { + Write-Warning -Message ('Unable to remove PSSession for Exchange Online - {0}' -f $_.Exception.Message) return } diff --git a/src/Disconnect-ExchangeOnlineProt.ps1 b/src/Disconnect-ExchangeOnlineProt.ps1 new file mode 100644 index 0000000..4e823a0 --- /dev/null +++ b/src/Disconnect-ExchangeOnlineProt.ps1 @@ -0,0 +1,21 @@ +function Disconnect-ExchangeOnlineProt { + + [CmdletBinding()] + param () + + try { + + $EXOProtnline = Get-ExchangeOnlineProtSession + + if ($null -ne $EXOProtnline) { + + Remove-PSSession -Session ($EXOProtnline) -ErrorAction Stop + Write-Verbose -Message 'The Exchange Online Protection PSSession is now closed.' -Verbose + } + } + catch { + + Write-Warning -Message ('Unable to remove PSSession for Exchange Online Protection - {0}' -f $_.Exception.Message) + return + } +} \ No newline at end of file diff --git a/src/DisconnectMsolServiceOnline.ps1 b/src/Disconnect-MsolServiceOnline.ps1 similarity index 89% rename from src/DisconnectMsolServiceOnline.ps1 rename to src/Disconnect-MsolServiceOnline.ps1 index 3300194..a6ccb70 100644 --- a/src/DisconnectMsolServiceOnline.ps1 +++ b/src/Disconnect-MsolServiceOnline.ps1 @@ -1,14 +1,18 @@ -function DisconnectMsolServiceOnline { +function Disconnect-MsolServiceOnline { + [CmdletBinding()] param () try { + if (Get-Module -Name 'MSOnline') { + Remove-Module -Name 'MSOnline' -ErrorAction Stop -WarningAction SilentlyContinue Write-Verbose -Message 'MsolService Module is now closed.' -Verbose } } catch { + Write-Warning -Message ('Unable to remove MsolService Module - {0}' -f $_.Exception.Message) return } diff --git a/src/Disconnect-Office365.ps1 b/src/Disconnect-Office365.ps1 index 71973cf..9c2fb3d 100644 --- a/src/Disconnect-Office365.ps1 +++ b/src/Disconnect-Office365.ps1 @@ -9,6 +9,7 @@ MsolService requraes a seprate module - http://go.microsoft.com/fwlink/?linkid=236297 Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 Skype for Business Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 + Exchange Online, Exchange Online Protection, Complince Center does not require seperate binaries. .EXAMPLE Disconnect-Office365 @@ -19,6 +20,7 @@ VERBOSE: MsolService Module is now closed. VERBOSE: The Compliance Center Online PSSession is now closed. VERBOSE: The Exchange Online PSSession is now closed. + VERBOSE: The Exchange Online Protection PSSession is now closed. VERBOSE: The Sharepoint Online Session is now closed. VERBOSE: The Skype for Business Online PSSession is now closed. @@ -29,10 +31,13 @@ VERBOSE: Disconnecting from AzureAD. + VERBOSE: Azure AD Session is now closed. VERBOSE: Disconnecting from Compliance Center. + VERBOSE: The Compliance Center Online PSSession is now closed. VERBOSE: Disconnecting from Exchange Online. + VERBOSE: The Exchange Online PSSession is now closed. - This command disconnects from AzureAD, Compliance and Exchange Online service sessions that are available and running. + This command disconnects from AzureAD, Compliance Center and Exchange Online service sessions that are available and running. .NOTES Created on: 2017-02-23 14:56 @@ -51,17 +56,17 @@ param( # Provide one or more Office 365 services to disconnect from. # Valid values are: - # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline', 'SkypeforBusinessOnline' + # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' [Parameter( ValueFromPipeline = $true )] - [ValidateSet('AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline')] + [ValidateSet('AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline')] [ValidateNotNullOrEmpty()] [string[]]$Service = @('AllServices') ) begin { - if (($service = $Service | Sort-Object -Unique).Count -gt 5) { + if (($service = $Service | Sort-Object -Unique).Count -gt 6) { $Service = 'AllServices' } } @@ -89,6 +94,10 @@ Write-Verbose -Message 'Disconnecting from Exchange Online.' -Verbose Disconnect-ExchangeOnline } + 'ExchangeOnlineProtection' { + Write-Verbose -Message 'Disconnecting from Exchange Online Protection.' -Verbose + Disconnect-ExchangeOnlineProt + } 'SharepointOnline' { Write-Verbose -Message 'Disconnecting from Sharepoint Online.' -Verbose Disconnect-SPOnline @@ -103,6 +112,7 @@ Disconnect-MsolServiceOnline Disconnect-CCOnline Disconnect-ExchangeOnline + Disconnect-ExchangeOnlineProt Disconnect-SPOnline Disconnect-SfBOnline } diff --git a/src/DisconnectSPOnline.ps1 b/src/Disconnect-SPOnline.ps1 similarity index 93% rename from src/DisconnectSPOnline.ps1 rename to src/Disconnect-SPOnline.ps1 index 0edcaed..71a89df 100644 --- a/src/DisconnectSPOnline.ps1 +++ b/src/Disconnect-SPOnline.ps1 @@ -1,14 +1,18 @@ -function DisconnectSPOnline { +function Disconnect-SPOnline { + [CmdletBinding()] param () try { + $null = Disconnect-SPOService -ErrorAction Stop Remove-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -Force -ErrorAction Stop Write-Verbose -Message 'The Sharepoint Online Session is now closed.' -Verbose } catch { + if ($_.Exception.Message -notmatch 'There is no service currently connected') { + Write-Warning -Message ('Unable to disconnect Sharepoint Online Session - {0}' -f $_.Exception.Message) return } diff --git a/src/DisconnectSfBOnline.ps1 b/src/Disconnect-SfBOnline.ps1 similarity index 67% rename from src/DisconnectSfBOnline.ps1 rename to src/Disconnect-SfBOnline.ps1 index ec88c75..4fc3015 100644 --- a/src/DisconnectSfBOnline.ps1 +++ b/src/Disconnect-SfBOnline.ps1 @@ -1,15 +1,21 @@ -function DisconnectSfBOnline { +function Disconnect-SfBOnline { + [CmdletBinding()] param () try { - if ($null -ne ($SbfoSession = Get-SfBOnlineSession)) { - Remove-PSSession -Session ($SbfoSession) -ErrorAction Stop + + $SfBOSession = Get-SfBOnlineSession + + if ($null -ne $SfBOSession) { + + Remove-PSSession -Session ($SfBOSession) -ErrorAction Stop Remove-Module -Name 'SkypeOnlineConnector' -Force -ErrorAction Stop Write-Verbose -Message 'The Skype for Business Online PSSession is now closed.' -Verbose } } catch { + Write-Warning -Message ('Unable to remove PSSession for Skype for Business Online - {0}' -f $_.Exception.Message) return } diff --git a/src/Get-AzureADCredential.ps1 b/src/Get-AzureADCredential.ps1 new file mode 100644 index 0000000..50714a5 --- /dev/null +++ b/src/Get-AzureADCredential.ps1 @@ -0,0 +1,41 @@ +function Get-AzureADCredential { + + [CmdletBinding()] + param ( + # Maximum amount of Azure Credential tries. + [uint16]$MaxTry = 3 + ) + + try { + + if ($null -eq $AzureADCredentials) { + + $Counter = 0 + do { + + $AzureADCredentials = Get-Credential -Message 'UserPrincipalName in Azure AD to access Office 365.' + if ($Counter -gt 0 -and $Counter -le $MaxTry) { + Write-Verbose -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' -Verbose + Write-Verbose -Message ('Try {0} of {1}' -f $Counter,$MaxTry) -Verbose + } + elseif ($Counter -ge $MaxTry) { + + Write-Error -Message 'Credentials does not match a UserPrincipalName in AzureAD' -Exception 'System.Management.Automation.SetValueException' -Category InvalidResult -ErrorAction Stop + break + } + + $Counter++ + } + # Regular expression for a valid UserPrincipalName. + while ($AzureADCredentials.UserName -notmatch ` + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") + + + } + } + catch { + + Write-Verbose -Message ('Problem with Credentials - {0}' -f $_.Exception.Message) + return $false + } +} \ No newline at end of file diff --git a/src/Get-CCOnlineSession.ps1 b/src/Get-CCOnlineSession.ps1 new file mode 100644 index 0000000..7eabb32 --- /dev/null +++ b/src/Get-CCOnlineSession.ps1 @@ -0,0 +1,15 @@ +function Get-CCOnlineSession { + + [CmdletBinding()] + param () + + try { + + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName Compliance -FilterConfigurationName Microsoft.Exchange + } + catch { + + Write-Warning -Message ('Unable to get active Compliance Center Online PSSession - {0}' -f $_.Exception.Message) + return $null + } +} \ No newline at end of file diff --git a/src/Get-ExchangeOnlineProtSession.ps1 b/src/Get-ExchangeOnlineProtSession.ps1 new file mode 100644 index 0000000..e9a3c06 --- /dev/null +++ b/src/Get-ExchangeOnlineProtSession.ps1 @@ -0,0 +1,15 @@ +function Get-ExchangeOnlineProtSession { + + [CmdletBinding()] + param () + + try { + + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName protection -FilterConfigurationName Microsoft.Exchange + } + catch { + + Write-Warning -Message ('Unable to get active Exchange Online Protection PSSession - {0}' -f $_.Exception.Message) + return $null + } +} \ No newline at end of file diff --git a/src/Get-ExchangeOnlineSession.ps1 b/src/Get-ExchangeOnlineSession.ps1 new file mode 100644 index 0000000..27254c3 --- /dev/null +++ b/src/Get-ExchangeOnlineSession.ps1 @@ -0,0 +1,15 @@ +function Get-ExchangeOnlineSession { + + [CmdletBinding()] + param () + + try { + + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName outlook.office365.com -FilterConfigurationName Microsoft.Exchange + } + catch { + + Write-Warning -Message ('Unable to get active Exchange Online PSSession - {0}' -f $_.Exception.Message) + return $null + } +} \ No newline at end of file diff --git a/src/Get-OnlinePSSession.ps1 b/src/Get-OnlinePSSession.ps1 new file mode 100644 index 0000000..2c72282 --- /dev/null +++ b/src/Get-OnlinePSSession.ps1 @@ -0,0 +1,32 @@ +function Get-OnlinePSSession { + + param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true, + HelpMessage = 'PSSessions' + )] + [Management.Automation.Runspaces.PSSession[]]$Session, + + [Parameter( + Mandatory = $true, + HelpMessage = 'Provide a ComputerName for a PSSession for filter usage.' + )] + [ValidateNotNullOrEmpty()] + [string]$FilterComputerName, + + [Parameter( + Mandatory = $true, + HelpMessage = 'Provide a ConfiguratioName for a PSSession for filter usage.' + )] + [ValidateNotNullOrEmpty()] + [string]$FilterConfigurationName + ) + + process { + + if ($Session.ComputerName -match $FilterComputerName -and $Session.ConfigurationName -eq $FilterConfigurationName) { + $Session + } + } +} \ No newline at end of file diff --git a/src/Get-SfBOnlineSession.ps1 b/src/Get-SfBOnlineSession.ps1 new file mode 100644 index 0000000..3a12fda --- /dev/null +++ b/src/Get-SfBOnlineSession.ps1 @@ -0,0 +1,15 @@ +function Get-SfBOnlineSession { + + [CmdletBinding()] + param () + + try { + + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName 'online.lync.com' -FilterConfigurationName 'Microsoft.PowerShell' + } + catch { + + Write-Warning -Message ('Unable to get active Skype for Business Online PSSession - {0}' -f $_.Exception.Message) + return $null + } +} \ No newline at end of file diff --git a/src/GetAzureADCredential.ps1 b/src/GetAzureADCredential.ps1 deleted file mode 100644 index b315bfa..0000000 --- a/src/GetAzureADCredential.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -function GetAzureADCredential { - [CmdletBinding()] - param () - - try { - if ($Script:AzureADCredentials -eq $false) { - $Counter = 0 - do { - $Script:AzureADCredentials = Get-Credential -Message 'UserPrincipalName in Azure AD to access Office 365.' - if ($Counter -gt 0 -and $Counter -lt 3) { - Write-Verbose -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' -Verbose - } - elseif ($Counter -gt 2) { - Write-Error -Message 'Credentials does not match a UserPrincipalName in AzureAD' -Exception 'System.Management.Automation.SetValueException' -Category InvalidResult -ErrorAction Stop - break - } - $Counter++ - } - while ($Script:AzureADCredentials.UserName -notmatch "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") - } - } - catch { - return - } - - return -} \ No newline at end of file diff --git a/src/GetCCOnlineSession.ps1 b/src/GetCCOnlineSession.ps1 deleted file mode 100644 index 582662c..0000000 --- a/src/GetCCOnlineSession.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -function GetCCOnlineSession { - [CmdletBinding()] - param () - - try { - $session = Get-PSSession -ErrorAction Stop | Where-Object -FilterScript {$_.ComputerName -match 'Compliance' -and $_.ConfigurationName -eq 'Microsoft.Exchange'} - } - catch { - Write-Warning -Message ('Unable to get active Compliance Center Online PSSession - {0}' -f $_.Exception.Message) - return $null - } - - return $session -} \ No newline at end of file diff --git a/src/GetExchangeOnlineSession.ps1 b/src/GetExchangeOnlineSession.ps1 deleted file mode 100644 index 33d1872..0000000 --- a/src/GetExchangeOnlineSession.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -function GetExchangeOnlineSession { - [CmdletBinding()] - param () - - try { - $session = Get-PSSession -ErrorAction Stop | Where-Object -FilterScript { - $_.ComputerName -match 'outlook.office365.com' -and $_.ConfigurationName -eq 'Microsoft.Exchange' - } - } - catch { - Write-Warning -Message ('Unable to get active Exchange Online PSSession - {0}' -f $_.Exception.Message) - return $null - } - - return $session -} \ No newline at end of file diff --git a/src/GetSfBOnlineSession.ps1 b/src/GetSfBOnlineSession.ps1 deleted file mode 100644 index 5386428..0000000 --- a/src/GetSfBOnlineSession.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -function GetSfBOnlineSession { - [CmdletBinding()] - param () - - try { - $Session = Get-PSSession -ErrorAction Stop | Where-Object -FilterScript { - $_.ComputerName -match 'online.lync.com' -and $_.ConfigurationName -eq 'Microsoft.PowerShell' - } - $Session - } - catch { - Write-Warning -Message ('Unable to get active Exchange Online PSSession - {0}' -f $_.Exception.Message) - return $null - } -} \ No newline at end of file From 23df8797563471c2f60313a13804339a40aa7cbc Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Wed, 17 May 2017 23:13:36 +0200 Subject: [PATCH 05/20] Inital testing, stuck with Credential passing between functions --- build.settings.ps1 | 2 +- src/Connect-AzureADOnline.ps1 | 9 +++-- src/Connect-CCOnline.ps1 | 9 +++-- src/Connect-ExchangeOnline.ps1 | 9 +++-- src/Connect-ExchangeOnlineProt.ps1 | 7 +++- src/Connect-MsolServiceOnline.ps1 | 7 +++- src/Connect-Office365.ps1 | 53 ++++++++++++++++-------------- src/Connect-SPOnline.ps1 | 6 +++- src/Connect-SfBOnline.ps1 | 9 +++-- src/Disconnect-Office365.ps1 | 2 +- src/Get-AzureADCredential.ps1 | 52 ++++++++++++++++------------- src/Office365Connect.psm1 | 10 ++++-- 12 files changed, 113 insertions(+), 62 deletions(-) diff --git a/build.settings.ps1 b/build.settings.ps1 index ed49a24..165c697 100644 --- a/build.settings.ps1 +++ b/build.settings.ps1 @@ -41,7 +41,7 @@ Properties { # Enable/disable use of PSScriptAnalyzer to perform script analysis. [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] - $ScriptAnalysisEnabled = $false + $ScriptAnalysisEnabled = $true # When PSScriptAnalyzer is enabled, control which severity level will generate a build failure. # Valid values are Error, Warning, Information and None. "None" will report errors but will not diff --git a/src/Connect-AzureADOnline.ps1 b/src/Connect-AzureADOnline.ps1 index 1f26e2f..d40b436 100644 --- a/src/Connect-AzureADOnline.ps1 +++ b/src/Connect-AzureADOnline.ps1 @@ -1,9 +1,14 @@ function Connect-AzureADOnline { [CmdletBinding()] - param( + param( + + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) $Module = Get-Module -Name AzureAD -ListAvailable diff --git a/src/Connect-CCOnline.ps1 b/src/Connect-CCOnline.ps1 index ac8c8a7..d466be5 100644 --- a/src/Connect-CCOnline.ps1 +++ b/src/Connect-CCOnline.ps1 @@ -1,9 +1,14 @@ function Connect-CCOnline { [CmdletBinding()] - param( + param( + + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) if ($null -ne (Get-CCOnlineSession)) { diff --git a/src/Connect-ExchangeOnline.ps1 b/src/Connect-ExchangeOnline.ps1 index dc74d66..e600602 100644 --- a/src/Connect-ExchangeOnline.ps1 +++ b/src/Connect-ExchangeOnline.ps1 @@ -1,9 +1,14 @@ function Connect-ExchangeOnline { [CmdletBinding()] - param( + param( + + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) if ($null -ne (Get-ExchangeOnlineSession)) { diff --git a/src/Connect-ExchangeOnlineProt.ps1 b/src/Connect-ExchangeOnlineProt.ps1 index b869fa5..b4fe435 100644 --- a/src/Connect-ExchangeOnlineProt.ps1 +++ b/src/Connect-ExchangeOnlineProt.ps1 @@ -2,8 +2,13 @@ [CmdletBinding()] param( + + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) if ($null -ne (Get-ExchangeOnlineProtSession)) { diff --git a/src/Connect-MsolServiceOnline.ps1 b/src/Connect-MsolServiceOnline.ps1 index 5bab5e4..181ab64 100644 --- a/src/Connect-MsolServiceOnline.ps1 +++ b/src/Connect-MsolServiceOnline.ps1 @@ -2,8 +2,13 @@ [CmdletBinding()] param( + + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) $Module = Get-Module -Name 'MSOnline' -ListAvailable diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 index ab91bb9..0057c62 100644 --- a/src/Connect-Office365.ps1 +++ b/src/Connect-Office365.ps1 @@ -123,59 +123,64 @@ # Sorting all input strings from the Service parameter. if (($Service = $Service | Sort-Object -Unique).Count -gt 6 -or $Service -eq 'All') { $Service = 'AllServices' - } + } + + if ($PSCmdlet.ShouldProcess('UserPrincipalName in Azure AD to access Office 365', 'Get-AzureADCredential')) { + + $Credential = Get-AzureADCredential + + if ($Credential -eq $false) { + + Write-Warning -Message 'Need valid credentials to connect, please provide the correct credentials.' + exit + } + } } process { foreach ($s in $Service) { - + $Credential.GetNetworkCredential() if ($PSCmdlet.ShouldProcess('Establishing a PowerShell session to {0} - Office 365.' -f ('{0}' -f $s), $MyInvocation.MyCommand.Name)) { - $null = Get-AzureADCredential - - if ($Script:AzureADCredentials -eq $false) { - Write-Warning -Message 'Need valid credentials to connect, please provide the correct credentials.' - break - } - + switch ($s) { 'AzureAD' { Write-Verbose -Message 'Conncting to AzureAD.' -Verbose - Connect-AzureADOnline + Connect-AzureADOnline -Credential $Credential } 'MSOnline' { Write-Verbose -Message 'Conncting to MSolService.' -Verbose - Connect-MsolServiceOnline + Connect-MsolServiceOnline -Credential $Credential } 'ComplianceCenter' { Write-Verbose -Message 'Conncting to Compliance Center.' -Verbose - Connect-CCOnline + Connect-CCOnline -Credential $Credential } 'ExchangeOnline' { Write-Verbose -Message 'Conncting to Exchange Online.' -Verbose - Connect-ExchangeOnline + Connect-ExchangeOnline -Credential $Credential } 'ExchangeOnlineProtection' { Write-Verbose -Message 'Conncting to Exchange Online Protection.' -Verbose - Connect-ExchangeOnlineProt + Connect-ExchangeOnlineProt -Credential $Credential } 'SharepointOnline' { Write-Verbose -Message 'Conncting to Sharepoint Online.' -Verbose - Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] + Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] -Credential $Credential } 'SkypeforBusinessOnline' { Write-Verbose -Message 'Conncting to Skype for Business Online.' -Verbose - Connect-SfBOnline + Connect-SfBOnline -Credential $Credential } Default { Write-Verbose -Message 'Connecting to all Office 365 Services.' -Verbose - Connect-AzureADOnline - Connect-MsolServiceOnline - Connect-CCOnline - Connect-ExchangeOnline - Connect-ExchangeOnlineProt - Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] - Connect-SfBOnline + Connect-AzureADOnline -Credential $Credential + Connect-MsolServiceOnline -Credential $Credential + Connect-CCOnline -Credential $Credential + Connect-ExchangeOnline -Credential $Credential + Connect-ExchangeOnlineProt -Credential $Credential + Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] -Credential $Credential + Connect-SfBOnline -Credential $Credential } } } @@ -183,6 +188,6 @@ } end { - Set-Variable -Name AzureADCredentials -Scope Script -Value $null -ErrorAction SilentlyContinue + Remove-Variable -Name Credential -ErrorAction SilentlyContinue } } \ No newline at end of file diff --git a/src/Connect-SPOnline.ps1 b/src/Connect-SPOnline.ps1 index d27cefc..8d0d025 100644 --- a/src/Connect-SPOnline.ps1 +++ b/src/Connect-SPOnline.ps1 @@ -9,8 +9,12 @@ [Alias('Domain','DomainHost','Customer')] [string]$SharepointDomain, + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) $Module = Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ListAvailable diff --git a/src/Connect-SfBOnline.ps1 b/src/Connect-SfBOnline.ps1 index 8468d4b..0947272 100644 --- a/src/Connect-SfBOnline.ps1 +++ b/src/Connect-SfBOnline.ps1 @@ -1,9 +1,14 @@ function Connect-SfBOnline { [CmdletBinding()] - param( + param( + + [Parameter( + Mandatory = $true, + HelpMessage = 'Credentials in Azure AD to access Office 365.' + )] [System.Management.Automation.Credential()] - [pscredential]$Credential = [pscredential]::Empty + [pscredential]$Credential ) $Module = Get-Module -Name 'SkypeOnlineConnector' -ListAvailable diff --git a/src/Disconnect-Office365.ps1 b/src/Disconnect-Office365.ps1 index 9c2fb3d..ff1da77 100644 --- a/src/Disconnect-Office365.ps1 +++ b/src/Disconnect-Office365.ps1 @@ -124,6 +124,6 @@ end { # If the saved credentials variables for some reason is not removed we remove them again. - Set-Variable -Name AzureADCredentials -Scope Script -Value $null -ErrorAction SilentlyContinue + Remove-Variable -Name Credential -ErrorAction SilentlyContinue } } \ No newline at end of file diff --git a/src/Get-AzureADCredential.ps1 b/src/Get-AzureADCredential.ps1 index 50714a5..e4cf391 100644 --- a/src/Get-AzureADCredential.ps1 +++ b/src/Get-AzureADCredential.ps1 @@ -1,37 +1,43 @@ function Get-AzureADCredential { - + + [OutputType([bool], [PSCredential])] [CmdletBinding()] param ( # Maximum amount of Azure Credential tries. [uint16]$MaxTry = 3 ) - - try { - - if ($null -eq $AzureADCredentials) { - - $Counter = 0 - do { - - $AzureADCredentials = Get-Credential -Message 'UserPrincipalName in Azure AD to access Office 365.' - if ($Counter -gt 0 -and $Counter -le $MaxTry) { - Write-Verbose -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' -Verbose - Write-Verbose -Message ('Try {0} of {1}' -f $Counter,$MaxTry) -Verbose - } - elseif ($Counter -ge $MaxTry) { + + $Regex = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" - Write-Error -Message 'Credentials does not match a UserPrincipalName in AzureAD' -Exception 'System.Management.Automation.SetValueException' -Category InvalidResult -ErrorAction Stop - break - } + try { - $Counter++ + $Counter = 1 + do { + + $AzureADCredential = Get-Credential -Message 'UserPrincipalName in Azure AD to access Office 365.' + + if ($AzureADCredential -match $Regex) { + + Write-Verbose -Message 'Credential match' + return $AzureADCredential + } + if ($Counter -lt $MaxTry) { + + Write-Verbose -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' -Verbose + Write-Verbose -Message ('Try {0} of {1}' -f $Counter, $MaxTry) -Verbose + } + elseif ($Counter -ge $MaxTry) { + + Write-Verbose -Message ('Try {0} of {1}' -f $Counter, $MaxTry) -Verbose + Write-Error -Message 'Credentials does not match a UserPrincipalName in AzureAD' -Exception 'System.Management.Automation.SetValueException' -Category InvalidResult -ErrorAction Stop + break } - # Regular expression for a valid UserPrincipalName. - while ($AzureADCredentials.UserName -notmatch ` - "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") - + $Counter++ } + # Regular expression for a valid UserPrincipalName. + while ($AzureADCredential.UserName -notmatch $Regex) + #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") } catch { diff --git a/src/Office365Connect.psm1 b/src/Office365Connect.psm1 index 8b7fe4e..0fea02b 100644 --- a/src/Office365Connect.psm1 +++ b/src/Office365Connect.psm1 @@ -1,5 +1,11 @@ # Implement your module commands in this script. - +$VerbNoun = '*-*' +$Functions = Get-ChildItem -Path $PSScriptRoot -Filter $VerbNoun +foreach ($f in $Functions) +{ + Write-Verbose -Message ("Importing function {0}." -f $f.FullName) + . $f.FullName +} # Export only the functions using PowerShell standard verb-noun naming. -Export-ModuleMember -Function *-* +Export-ModuleMember -Function $VerbNoun From ab5cd45df45cc591efa4a52279f907442f3658e4 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 09:44:54 +0200 Subject: [PATCH 06/20] Addming Peter tests and .Vscode folders --- .vscode/settings.json | 13 +++++ .vscode/tasks.json | 121 ++++++++++++++++++++++++++++++++++++++++++ test/Shared.ps1 | 11 ++++ test/Test.Tests.ps1 | 11 ++++ 4 files changed, 156 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 test/Shared.ps1 create mode 100644 test/Test.Tests.ps1 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..21f9212 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + //-------- Files configuration -------- + + // When enabled, will trim trailing whitespace when you save a file. + "files.trimTrailingWhitespace": true, + + + //-------- PowerShell Configuration -------- + + // Use a custom PowerShell Script Analyzer settings file for this workspace. + // Relative paths for this setting are always relative to the workspace root dir. + "powershell.scriptAnalysis.settingsPath": "src/ScriptAnalyzerSettings.psd1" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..c74eeee --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,121 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${relativeFile}: the current opened file relative to workspaceRoot +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + + // Start PowerShell + "windows": { + "command": "${env.windir}\\sysnative\\windowspowershell\\v1.0\\PowerShell.exe" + }, + "linux": { + "command": "/usr/bin/powershell" + }, + "osx": { + "command": "/usr/local/bin/powershell" + }, + + // The command is a shell script + "isShellCommand": true, + + // Show the output window always + "showOutput": "always", + + "args": [ + "-NoProfile", "-ExecutionPolicy", "Bypass" + ], + + // Associate with test task runner + "tasks": [ + { + "taskName": "Clean", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking psake on build.psake.ps1 -taskList Clean'; Invoke-psake build.psake.ps1 -taskList Clean;", + "Invoke-Command { Write-Host 'Completed Clean task in task runner.' }" + ] + }, + { + "taskName": "Build", + "suppressTaskName": true, + "isBuildCommand": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking psake on build.psake.ps1 -taskList Build'; Invoke-psake build.psake.ps1 -taskList Build;", + "Invoke-Command { Write-Host 'Completed Build task in task runner.' }" + ] + }, + { + "taskName": "BuildHelp", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking psake on build.psake.ps1 -taskList BuildHelp'; Invoke-psake build.psake.ps1 -taskList BuildHelp;", + "Invoke-Command { Write-Host 'Completed BuildHelp task in task runner.' }" + ] + }, + { + "taskName": "Analyze", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking psake on build.psake.ps1 -taskList Analyze'; Invoke-psake build.psake.ps1 -taskList Analyze;", + "Invoke-Command { Write-Host 'Completed Analyze task in task runner.' }" + ] + }, + { + "taskName": "Install", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking psake on build.psake.ps1 -taskList Install'; Invoke-psake build.psake.ps1 -taskList Install;", + "Invoke-Command { Write-Host 'Completed Install task in task runner.' }" + ] + }, + { + "taskName": "Publish", + "suppressTaskName": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking psake on build.psake.ps1 -taskList Publish'; Invoke-psake build.psake.ps1 -taskList Publish;", + "Invoke-Command { Write-Host 'Completed Publish task in task runner.' }" + ] + }, + { + "taskName": "Test", + "suppressTaskName": true, + "isTestCommand": true, + "showOutput": "always", + "args": [ + "Write-Host 'Invoking Pester'; Invoke-Pester -PesterOption @{IncludeVSCodeMarker=$true};", + "Invoke-Command { Write-Host 'Completed Test task in task runner.' }" + ], + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": ["absolute"], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "^\\s+at\\s+[^,]+,\\s*(.*?):\\s+line\\s+(\\d+)$", + "file": 1, + "line": 2 + } + ] + } + ] + } + ] +} diff --git a/test/Shared.ps1 b/test/Shared.ps1 new file mode 100644 index 0000000..34ef68b --- /dev/null +++ b/test/Shared.ps1 @@ -0,0 +1,11 @@ +# Dot source this script in any Pester test script that requires the module to be imported. + +$ModuleManifestName = 'Office365Connect.psd1' +$ModuleManifestPath = "$PSScriptRoot\..\src\$ModuleManifestName" + +if (!$SuppressImportModule) { + # -Scope Global is needed when running tests from inside of psake, otherwise + # the module's functions cannot be found in the Test\ namespace + Import-Module $ModuleManifestPath -Scope Global +} + diff --git a/test/Test.Tests.ps1 b/test/Test.Tests.ps1 new file mode 100644 index 0000000..49cd234 --- /dev/null +++ b/test/Test.Tests.ps1 @@ -0,0 +1,11 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '', Scope='*', Target='SuppressImportModule')] +$SuppressImportModule = $true +. $PSScriptRoot\Shared.ps1 + +Describe 'Module Manifest Tests' { + It 'Passes Test-ModuleManifest' { + Test-ModuleManifest -Path $ModuleManifestPath + $? | Should Be $true + } +} + From f32bc73b1b98279c60bf143ff495f708c2bd9da4 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 13:38:31 +0200 Subject: [PATCH 07/20] Successfull testing and fix for Credential passing beteween functions --- src/Connect-AzureADOnline.ps1 | 3 +- src/Connect-CCOnline.ps1 | 10 ++++-- src/Connect-ExchangeOnline.ps1 | 12 ++++++-- src/Connect-ExchangeOnlineProt.ps1 | 12 ++++++-- src/Connect-MsolServiceOnline.ps1 | 25 ++++++++++++--- src/Connect-Office365.ps1 | 46 ++++++++++++++++------------ src/Connect-SPOnline.ps1 | 3 +- src/Connect-SfBOnline.ps1 | 3 +- src/Disconnect-MsolServiceOnline.ps1 | 29 ++++++++++++++++-- src/Get-AzureADCredential.ps1 | 12 ++++---- src/Get-OnlinePSSession.ps1 | 7 +++-- 11 files changed, 117 insertions(+), 45 deletions(-) diff --git a/src/Connect-AzureADOnline.ps1 b/src/Connect-AzureADOnline.ps1 index d40b436..a4a9488 100644 --- a/src/Connect-AzureADOnline.ps1 +++ b/src/Connect-AzureADOnline.ps1 @@ -4,11 +4,12 @@ param( [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) $Module = Get-Module -Name AzureAD -ListAvailable diff --git a/src/Connect-CCOnline.ps1 b/src/Connect-CCOnline.ps1 index d466be5..3ac97fe 100644 --- a/src/Connect-CCOnline.ps1 +++ b/src/Connect-CCOnline.ps1 @@ -4,11 +4,12 @@ param( [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) if ($null -ne (Get-CCOnlineSession)) { @@ -19,11 +20,16 @@ Write-Verbose -Message 'Disconnect from the current session to start a new one.' return } + else + { + Write-Warning -Message 'Compliance Center Online is not available on the target Office 365 tenant' + return + } } try { - $null = New-PSSession -ConfigurationName 'Microsoft.Exchange' ` -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` + $null = New-PSSession -ConfigurationName 'Microsoft.Exchange' ` -Name 'CCOOnline' ` -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection:$true ` -ErrorAction Stop ` -WarningAction SilentlyContinue } diff --git a/src/Connect-ExchangeOnline.ps1 b/src/Connect-ExchangeOnline.ps1 index e600602..bd46a22 100644 --- a/src/Connect-ExchangeOnline.ps1 +++ b/src/Connect-ExchangeOnline.ps1 @@ -4,26 +4,32 @@ param( [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) if ($null -ne (Get-ExchangeOnlineSession)) { - if (Get-Command -Name 'Get-Mailbox') { + if (Get-Command -Name 'Get-Mailbox' -ErrorAction SilentlyContinue) { Write-Verbose -Message 'Exchange Online PowerShell session already existis.' -Verbose Write-Verbose -Message 'Disconnect from the current session to start a new one.' return } + else + { + Write-Warning -Message 'Exchange Online is not available on the target Office 365 tenant' + return + } } try { - $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -Name 'ExchangeOnline' ` -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue } catch { diff --git a/src/Connect-ExchangeOnlineProt.ps1 b/src/Connect-ExchangeOnlineProt.ps1 index b4fe435..590e1ec 100644 --- a/src/Connect-ExchangeOnlineProt.ps1 +++ b/src/Connect-ExchangeOnlineProt.ps1 @@ -4,26 +4,32 @@ param( [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) if ($null -ne (Get-ExchangeOnlineProtSession)) { - if (Get-Command -Name 'Set-EOPUser') { + if (Get-Command -Name 'Set-EOPUser' -ErrorAction SilentlyContinue) { Write-Verbose -Message 'Exchange Online Protection PowerShell session already existis.' -Verbose Write-Verbose -Message 'Disconnect from the current session to start a new one.' return } + else + { + Write-Warning -Message 'Exchange Online Protection is not available on the target Office 365 tenant' + return + } } try { - $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -ConnectionUri 'https://ps.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -Name 'ExchangeOnlineProt' ` -ConnectionUri 'https://ps.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue } catch { diff --git a/src/Connect-MsolServiceOnline.ps1 b/src/Connect-MsolServiceOnline.ps1 index 181ab64..bfeb582 100644 --- a/src/Connect-MsolServiceOnline.ps1 +++ b/src/Connect-MsolServiceOnline.ps1 @@ -4,11 +4,12 @@ param( [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) $Module = Get-Module -Name 'MSOnline' -ListAvailable @@ -36,9 +37,25 @@ Connect-MsolService -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue } catch { - - Write-Warning -Message ('Unable to connect to MSOnline - {0}' -f $_.Exception.Message) - return + # If Connect-MsolService fails. First fail, try to remove Cookies. Second fail, try to restart Microsoft Online Services Sign-in Assistant + try { + + Disconnect-MsolServiceOnline -CoockiesOnly + Connect-MsolService -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + + try { + + Restart-Service -Name 'msoidsvc' -ErrorAction Stop + Connect-MsolService -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue + } + catch { + + Write-Warning -Message ('Unable to connect to MSOnline - {0}' -f $_.Exception.Message) + return + } + } } } } \ No newline at end of file diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 index 0057c62..e5a4c22 100644 --- a/src/Connect-Office365.ps1 +++ b/src/Connect-Office365.ps1 @@ -93,19 +93,19 @@ if ($Service -match 'AllServices|SharepointOnline') { - # Create a ParameterAttribute Object + # Create a Parameter Attribute Object $SPAttrib = New-Object -TypeName System.Management.Automation.ParameterAttribute $SPAttrib.Position = 1 $SPAttrib.Mandatory = $true $SPAttrib.HelpMessage = 'Enter a valid Sharepoint Online Domain. Example: "Contoso"' - # Create an AliasAttribute Object for the parameter + # Create an Alias Attribute Object for the parameter $SPAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList @('Domain','DomainHost','Customer') # Create an AttributeCollection Object $SPCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] - # Add the attributes to the AttributeCollection + # Add the attributes and aliases to the Attribute Collection $SPCollection.Add($SPAttrib) $SPCollection.Add($SPAlias) @@ -119,12 +119,21 @@ } } begin { + + $EOPExclusive = 'Will not use Exchange Online Protection. EOP and EO are mutually exclusive.' # Sorting all input strings from the Service parameter. - if (($Service = $Service | Sort-Object -Unique).Count -gt 6 -or $Service -eq 'All') { + if (([Collections.ArrayList]$Service = @($Service | Sort-Object -Unique)).Count -gt 6 -or $Service -match 'AllServices') { $Service = 'AllServices' + Write-Verbose -Message $EOPExclusive } + if ($Service -match 'ExchangeOnline' -and $Service -match 'ExchangeOnlineProtection') + { + Write-Verbose -Message $EOPExclusive + $Service.Remove('ExchangeOnlineProtection') + } + if ($PSCmdlet.ShouldProcess('UserPrincipalName in Azure AD to access Office 365', 'Get-AzureADCredential')) { $Credential = Get-AzureADCredential @@ -139,48 +148,47 @@ process { foreach ($s in $Service) { - $Credential.GetNetworkCredential() + if ($PSCmdlet.ShouldProcess('Establishing a PowerShell session to {0} - Office 365.' -f ('{0}' -f $s), $MyInvocation.MyCommand.Name)) { switch ($s) { 'AzureAD' { Write-Verbose -Message 'Conncting to AzureAD.' -Verbose - Connect-AzureADOnline -Credential $Credential + $Credential | Connect-AzureADOnline } 'MSOnline' { Write-Verbose -Message 'Conncting to MSolService.' -Verbose - Connect-MsolServiceOnline -Credential $Credential + $Credential | Connect-MsolServiceOnline } 'ComplianceCenter' { Write-Verbose -Message 'Conncting to Compliance Center.' -Verbose - Connect-CCOnline -Credential $Credential + $Credential | Connect-CCOnline } 'ExchangeOnline' { Write-Verbose -Message 'Conncting to Exchange Online.' -Verbose - Connect-ExchangeOnline -Credential $Credential + $Credential | Connect-ExchangeOnline } 'ExchangeOnlineProtection' { Write-Verbose -Message 'Conncting to Exchange Online Protection.' -Verbose - Connect-ExchangeOnlineProt -Credential $Credential + $Credential | Connect-ExchangeOnlineProt } 'SharepointOnline' { Write-Verbose -Message 'Conncting to Sharepoint Online.' -Verbose - Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] -Credential $Credential + $Credential | Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] } 'SkypeforBusinessOnline' { Write-Verbose -Message 'Conncting to Skype for Business Online.' -Verbose - Connect-SfBOnline -Credential $Credential + $Credential | Connect-SfBOnline } Default { Write-Verbose -Message 'Connecting to all Office 365 Services.' -Verbose - Connect-AzureADOnline -Credential $Credential - Connect-MsolServiceOnline -Credential $Credential - Connect-CCOnline -Credential $Credential - Connect-ExchangeOnline -Credential $Credential - Connect-ExchangeOnlineProt -Credential $Credential - Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] -Credential $Credential - Connect-SfBOnline -Credential $Credential + $Credential | Connect-AzureADOnline + $Credential | Connect-MsolServiceOnline + $Credential | Connect-CCOnline + $Credential | Connect-ExchangeOnline + $Credential | Connect-SPOnline -SharepointDomain $PSBoundParameters['SharepointDomain'] + $Credential | Connect-SfBOnline } } } diff --git a/src/Connect-SPOnline.ps1 b/src/Connect-SPOnline.ps1 index 8d0d025..a8012ca 100644 --- a/src/Connect-SPOnline.ps1 +++ b/src/Connect-SPOnline.ps1 @@ -10,11 +10,12 @@ [string]$SharepointDomain, [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) $Module = Get-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -ListAvailable diff --git a/src/Connect-SfBOnline.ps1 b/src/Connect-SfBOnline.ps1 index 0947272..e951242 100644 --- a/src/Connect-SfBOnline.ps1 +++ b/src/Connect-SfBOnline.ps1 @@ -4,11 +4,12 @@ param( [Parameter( + ValueFromPipeline = $true, Mandatory = $true, HelpMessage = 'Credentials in Azure AD to access Office 365.' )] [System.Management.Automation.Credential()] - [pscredential]$Credential + [PSCredential]$Credential ) $Module = Get-Module -Name 'SkypeOnlineConnector' -ListAvailable diff --git a/src/Disconnect-MsolServiceOnline.ps1 b/src/Disconnect-MsolServiceOnline.ps1 index a6ccb70..c300fa6 100644 --- a/src/Disconnect-MsolServiceOnline.ps1 +++ b/src/Disconnect-MsolServiceOnline.ps1 @@ -1,7 +1,30 @@ function Disconnect-MsolServiceOnline { - [CmdletBinding()] - param () + [CmdletBinding( + SupportsShouldProcess = $true + )] + param ( + [switch]$CoockiesOnly + ) + + try { + + $Cookies = ([Environment]::GetFolderPath('Cookies')) | Get-ChildItem -Recurse | Select-String -Pattern 'MicrosoftOnline' | Group-Object -Property Path + foreach ($c in $Cookies.Name) { + + if ($PSCmdlet.ShouldProcess(('Removing coockie {0} for MSOnline saved credentials' -f $c.Name),'Remove-Item')) { + Remove-Item -Path $c -ErrorAction SilentlyContinue + } + } + + if ($PSBoundParameters.ContainsKey('CoockiesOnly')) { + return + } + } + catch { + + Write-Verbose -Message 'Unable to remove MicrosoftOnline cookies.' + } try { @@ -9,7 +32,7 @@ Remove-Module -Name 'MSOnline' -ErrorAction Stop -WarningAction SilentlyContinue Write-Verbose -Message 'MsolService Module is now closed.' -Verbose - } + } } catch { diff --git a/src/Get-AzureADCredential.ps1 b/src/Get-AzureADCredential.ps1 index e4cf391..cf7f375 100644 --- a/src/Get-AzureADCredential.ps1 +++ b/src/Get-AzureADCredential.ps1 @@ -16,19 +16,19 @@ $AzureADCredential = Get-Credential -Message 'UserPrincipalName in Azure AD to access Office 365.' - if ($AzureADCredential -match $Regex) { + if ($AzureADCredential.UserName -match $Regex) { - Write-Verbose -Message 'Credential match' - return $AzureADCredential + Write-Verbose -Message 'Credential match the regex.' + $AzureADCredential + break } if ($Counter -lt $MaxTry) { - Write-Verbose -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' -Verbose - Write-Verbose -Message ('Try {0} of {1}' -f $Counter, $MaxTry) -Verbose + Write-Warning -Message 'Credentials does not match a valid UserPrincipalName in AzureAD, please provide a corrent UserPrincipalName.' + Write-Warning -Message ('Try {0} of {1}' -f ($Counter + 1), $MaxTry) } elseif ($Counter -ge $MaxTry) { - Write-Verbose -Message ('Try {0} of {1}' -f $Counter, $MaxTry) -Verbose Write-Error -Message 'Credentials does not match a UserPrincipalName in AzureAD' -Exception 'System.Management.Automation.SetValueException' -Category InvalidResult -ErrorAction Stop break } diff --git a/src/Get-OnlinePSSession.ps1 b/src/Get-OnlinePSSession.ps1 index 2c72282..8eb8294 100644 --- a/src/Get-OnlinePSSession.ps1 +++ b/src/Get-OnlinePSSession.ps1 @@ -20,12 +20,15 @@ HelpMessage = 'Provide a ConfiguratioName for a PSSession for filter usage.' )] [ValidateNotNullOrEmpty()] - [string]$FilterConfigurationName + [string]$FilterConfigurationName, + + [ValidateNotNullOrEmpty()] + [string]$FilterSessionName = '.' ) process { - if ($Session.ComputerName -match $FilterComputerName -and $Session.ConfigurationName -eq $FilterConfigurationName) { + if ($Session.ComputerName -match $FilterComputerName -and $Session.ConfigurationName -eq $FilterConfigurationName -and $Session.Name -match $FilterSessionName) { $Session } } From 3712e825cccdf901de713a8d8abc6e6f815bee15 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 15:31:19 +0200 Subject: [PATCH 08/20] Resolve Connect-MSOnline with saved credentials --- src/Connect-MsolServiceOnline.ps1 | 16 +++++++++++++--- src/Connect-Office365.ps1 | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Connect-MsolServiceOnline.ps1 b/src/Connect-MsolServiceOnline.ps1 index bfeb582..dc304cb 100644 --- a/src/Connect-MsolServiceOnline.ps1 +++ b/src/Connect-MsolServiceOnline.ps1 @@ -46,9 +46,19 @@ catch { try { - - Restart-Service -Name 'msoidsvc' -ErrorAction Stop - Connect-MsolService -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue + + $User = [Security.Principal.WindowsIdentity]::GetCurrent() + if ((New-Object Security.Principal.WindowsPrincipal $User).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { + + Write-Verbose -Message 'Restarting "Microsoft Online Services Sign-in Assistant" to avoid saved sessions.' + Restart-Service -Name 'msoidsvc' -ErrorAction Stop + Connect-MsolService -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue + } + else { + + Write-Warning -Message 'Unable to restart service "Microsoft Online Services Sign-in Assistant". Administrator privileges is required for this.' + return + } } catch { diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 index e5a4c22..d6e7f7f 100644 --- a/src/Connect-Office365.ps1 +++ b/src/Connect-Office365.ps1 @@ -122,8 +122,8 @@ $EOPExclusive = 'Will not use Exchange Online Protection. EOP and EO are mutually exclusive.' - # Sorting all input strings from the Service parameter. - if (([Collections.ArrayList]$Service = @($Service | Sort-Object -Unique)).Count -gt 6 -or $Service -match 'AllServices') { + # Sorting all input strings from the Service parameter to avoid duplicates. + if (([Collections.ArrayList]@($Service = $Service | Sort-Object -Unique)).Count -gt 6 -or $Service -match 'AllServices') { $Service = 'AllServices' Write-Verbose -Message $EOPExclusive } From b8e56984a685284e69e6bbf8a7cc47487b9551b0 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 15:37:03 +0200 Subject: [PATCH 09/20] Add -Verbose:$false to all Connect/Import-cmdlets for Modules and PSSessions --- src/Connect-AzureADOnline.ps1 | 4 ++-- src/Connect-CCOnline.ps1 | 7 ++++--- src/Connect-ExchangeOnline.ps1 | 6 ++++-- src/Connect-ExchangeOnlineProt.ps1 | 6 ++++-- src/Connect-MsolServiceOnline.ps1 | 2 +- src/Connect-SPOnline.ps1 | 5 +++-- src/Connect-SfBOnline.ps1 | 6 +++--- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Connect-AzureADOnline.ps1 b/src/Connect-AzureADOnline.ps1 index a4a9488..3948a4c 100644 --- a/src/Connect-AzureADOnline.ps1 +++ b/src/Connect-AzureADOnline.ps1 @@ -24,7 +24,7 @@ try { - Import-Module -Name 'AzureAD' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + Import-Module -Name 'AzureAD' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { @@ -34,7 +34,7 @@ try { - $null = Connect-AzureAD -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue + $null = Connect-AzureAD -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { diff --git a/src/Connect-CCOnline.ps1 b/src/Connect-CCOnline.ps1 index 3ac97fe..44dc9ab 100644 --- a/src/Connect-CCOnline.ps1 +++ b/src/Connect-CCOnline.ps1 @@ -31,7 +31,8 @@ $null = New-PSSession -ConfigurationName 'Microsoft.Exchange' ` -Name 'CCOOnline' ` -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection:$true ` -ErrorAction Stop ` - -WarningAction SilentlyContinue + -WarningAction SilentlyContinue ` + -Verbose:$false } catch { @@ -41,10 +42,10 @@ try { - $null = Import-Module ` (Import-PSSession -Session (Get-CCOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) ` + $null = Import-Module ` (Import-PSSession -Session (Get-CCOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false) ` -DisableNameChecking ` -Global ` - -ErrorAction Stop ` -WarningAction SilentlyContinue + -ErrorAction Stop ` -WarningAction SilentlyContinue ` -Verbose:$false } catch { diff --git a/src/Connect-ExchangeOnline.ps1 b/src/Connect-ExchangeOnline.ps1 index bd46a22..eaf14ee 100644 --- a/src/Connect-ExchangeOnline.ps1 +++ b/src/Connect-ExchangeOnline.ps1 @@ -29,7 +29,8 @@ try { - $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -Name 'ExchangeOnline' ` -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -Name 'ExchangeOnline' ` -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue ` + -Verbose:$false } catch { @@ -39,7 +40,8 @@ try { - $null = Import-Module ` (Import-PSSession -Session (Get-ExchangeOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) ` -DisableNameChecking ` -Global ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = Import-Module ` (Import-PSSession -Session (Get-ExchangeOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false) ` -DisableNameChecking ` -Global ` -ErrorAction Stop ` -WarningAction SilentlyContinue ` + -Verbose:$false } catch { diff --git a/src/Connect-ExchangeOnlineProt.ps1 b/src/Connect-ExchangeOnlineProt.ps1 index 590e1ec..4acaa15 100644 --- a/src/Connect-ExchangeOnlineProt.ps1 +++ b/src/Connect-ExchangeOnlineProt.ps1 @@ -29,7 +29,8 @@ try { - $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -Name 'ExchangeOnlineProt' ` -ConnectionUri 'https://ps.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = New-PSSession -ConfigurationName Microsoft.Exchange ` -Name 'ExchangeOnlineProt' ` -ConnectionUri 'https://ps.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection ` -ErrorAction Stop ` -WarningAction SilentlyContinue ` + -Verbose:$false } catch { @@ -39,7 +40,8 @@ try { - $null = Import-Module ` (Import-PSSession -Session (Get-ExchangeOnlineProtSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue) ` -DisableNameChecking ` -Global ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = Import-Module ` (Import-PSSession -Session (Get-ExchangeOnlineProtSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false) ` -DisableNameChecking ` -Global ` -ErrorAction Stop ` -WarningAction SilentlyContinue ` + -Verbose:$false } catch { diff --git a/src/Connect-MsolServiceOnline.ps1 b/src/Connect-MsolServiceOnline.ps1 index dc304cb..1ee6595 100644 --- a/src/Connect-MsolServiceOnline.ps1 +++ b/src/Connect-MsolServiceOnline.ps1 @@ -24,7 +24,7 @@ try { - Import-Module -Name 'MSOnline' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + Import-Module -Name 'MSOnline' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { diff --git a/src/Connect-SPOnline.ps1 b/src/Connect-SPOnline.ps1 index a8012ca..4d5a9d8 100644 --- a/src/Connect-SPOnline.ps1 +++ b/src/Connect-SPOnline.ps1 @@ -30,7 +30,7 @@ try { - Import-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + Import-Module -Name 'Microsoft.Online.SharePoint.PowerShell' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { @@ -40,7 +40,8 @@ try { - $null = Connect-SPOService -Url ('https://{0}-admin.sharepoint.com' -f ($SharepointDomain)) ` -Credential $Credential ` -ErrorAction Stop ` -WarningAction SilentlyContinue + $null = Connect-SPOService -Url ('https://{0}-admin.sharepoint.com' -f ($SharepointDomain)) ` -Credential $Credential ` -ErrorAction Stop ` -WarningAction SilentlyContinue ` + -Verbose:$false } catch { diff --git a/src/Connect-SfBOnline.ps1 b/src/Connect-SfBOnline.ps1 index e951242..453fbdd 100644 --- a/src/Connect-SfBOnline.ps1 +++ b/src/Connect-SfBOnline.ps1 @@ -30,7 +30,7 @@ } try { - Import-Module -Name 'SkypeOnlineConnector' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue + Import-Module -Name 'SkypeOnlineConnector' -DisableNameChecking -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { @@ -40,7 +40,7 @@ try { - $null = New-CsOnlineSession -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue + $null = New-CsOnlineSession -Credential $Credential -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { @@ -49,7 +49,7 @@ } try { - $null = Import-PSSession -Session (Get-SfBOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue + $null = Import-PSSession -Session (Get-SfBOnlineSession) -DisableNameChecking -AllowClobber -ErrorAction Stop -WarningAction SilentlyContinue -Verbose:$false } catch { From e526f1b0d9e9e59baf5bdce2cab28a6b83fc97b7 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 15:43:17 +0200 Subject: [PATCH 10/20] Replace exit with break statement --- src/Connect-Office365.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 index d6e7f7f..b9c1faf 100644 --- a/src/Connect-Office365.ps1 +++ b/src/Connect-Office365.ps1 @@ -141,7 +141,7 @@ if ($Credential -eq $false) { Write-Warning -Message 'Need valid credentials to connect, please provide the correct credentials.' - exit + break } } } From ebc19ccd6881707cb0a1d8def4a0e2a563663e80 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 15:52:56 +0200 Subject: [PATCH 11/20] Add -FilterSessionName 'SessionName' where it was missing. --- src/Connect-CCOnline.ps1 | 2 +- src/Get-CCOnlineSession.ps1 | 2 +- src/Get-ExchangeOnlineProtSession.ps1 | 2 +- src/Get-ExchangeOnlineSession.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Connect-CCOnline.ps1 b/src/Connect-CCOnline.ps1 index 44dc9ab..9350d08 100644 --- a/src/Connect-CCOnline.ps1 +++ b/src/Connect-CCOnline.ps1 @@ -29,7 +29,7 @@ try { - $null = New-PSSession -ConfigurationName 'Microsoft.Exchange' ` -Name 'CCOOnline' ` -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` + $null = New-PSSession -ConfigurationName 'Microsoft.Exchange' ` -Name 'CCOnline' ` -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid/' ` -Credential $Credential ` -Authentication Basic ` -AllowRedirection:$true ` -ErrorAction Stop ` -WarningAction SilentlyContinue ` -Verbose:$false diff --git a/src/Get-CCOnlineSession.ps1 b/src/Get-CCOnlineSession.ps1 index 7eabb32..f180f78 100644 --- a/src/Get-CCOnlineSession.ps1 +++ b/src/Get-CCOnlineSession.ps1 @@ -5,7 +5,7 @@ try { - Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName Compliance -FilterConfigurationName Microsoft.Exchange + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName Compliance -FilterConfigurationName Microsoft.Exchange -FilterSessionName CCOnline } catch { diff --git a/src/Get-ExchangeOnlineProtSession.ps1 b/src/Get-ExchangeOnlineProtSession.ps1 index e9a3c06..981c8e1 100644 --- a/src/Get-ExchangeOnlineProtSession.ps1 +++ b/src/Get-ExchangeOnlineProtSession.ps1 @@ -5,7 +5,7 @@ try { - Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName protection -FilterConfigurationName Microsoft.Exchange + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName 'protection' -FilterConfigurationName 'Microsoft.Exchange' -FilterSessionName 'ExchangeOnlineProt' } catch { diff --git a/src/Get-ExchangeOnlineSession.ps1 b/src/Get-ExchangeOnlineSession.ps1 index 27254c3..cf4e808 100644 --- a/src/Get-ExchangeOnlineSession.ps1 +++ b/src/Get-ExchangeOnlineSession.ps1 @@ -5,7 +5,7 @@ try { - Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName outlook.office365.com -FilterConfigurationName Microsoft.Exchange + Get-PSSession -ErrorAction Stop | Get-OnlinePSSession -FilterComputerName outlook.office365.com -FilterConfigurationName Microsoft.Exchange -FilterSessionName 'ExchangeOnline' } catch { From 2823d43d377018dbdc4014c1fb0885f795117027 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 16:29:13 +0200 Subject: [PATCH 12/20] Correct links in Module manifest --- src/Office365Connect.psd1 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Office365Connect.psd1 b/src/Office365Connect.psd1 index ce15c2e..e0c8c0c 100644 --- a/src/Office365Connect.psd1 +++ b/src/Office365Connect.psd1 @@ -44,16 +44,16 @@ PrivateData = @{ Tags = 'AzureAD', 'Office365', 'Connect', 'Msol', 'ExchangeOnline', 'Online' # A URL to the license for this module. - LicenseUri = 'https://github.com/PhilipHaglund/PowerShell/blob/master/Office365Connect/LICENSE.txt' + LicenseUri = 'https://github.com/PhilipHaglund/Office365Connect/blob/master/LICENSE.txt' # A URL to the main website for this project. - ProjectUri = 'https://github.com/PhilipHaglund/PowerShell/tree/master/Office365Connect' + ProjectUri = 'https://github.com/PhilipHaglund/Office365Connect' # A URL to an icon representing this module. - IconUri = 'https://raw.githubusercontent.com/PhilipHaglund/PowerShell/master/Office365Connect/docs/pics/Office365Connect.jpg' + IconUri = 'https://raw.githubusercontent.com/PhilipHaglund/Office365Connect/tree/master/docs/pics/Office365Connect.jpg' # ReleaseNotes of this module - ReleaseNotes = 'https://github.com/PhilipHaglund/PowerShell/blob/master/Office365Connect/ReleaseNotes.md' + ReleaseNotes = 'https://github.com/PhilipHaglund/Office365Connect/blob/master/ReleaseNotes.md' # Indicates this is a pre-release/testing version of the module. IsPrerelease = 'False' @@ -63,8 +63,7 @@ PrivateData = @{ } # End of PrivateData hashtable # HelpInfo URI of this module -HelpInfoURI = 'https://github.com/PhilipHaglund/PowerShell/master/Office365Connect/docs/about_Office365Connect.help.md' - +HelpInfoURI = 'https://github.com/PhilipHaglund/Office365Connect/tree/master/docs/en-US/about_Office365Connect.help.md' } From c4f9fbff2d803a4098e971edca157e1f10e48ef4 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 16:34:14 +0200 Subject: [PATCH 13/20] First release of releasenotes.md --- ReleaseNotes.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index e69de29..6d142c6 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -0,0 +1,15 @@ +## What is New in Plaster 1.5.0 +May 18, 2017 + +- Complete refactor and restructure of the module and its functions. +- Each function has it's own .ps1 file. +- Use cuddeling for script blocks since it's more popular. +- A new module baseline using [Plaster](https://github.com/PowerShell/Plaster "Plaster"). +- Use a own repository instead of my general PowerShell [repository](https://github.com/PhilipHaglund/PowerShell "repository"). +- New module version order using 3 numbers (1.5.0) instead of four (1.0.1.1). +- Add `Exchange Online Protection` as an available service to connect to. + + + +### Feedback +Please send your feedback to [https://github.com/PhilipHaglund/Office365Connect/issues](https://github.com/PhilipHaglund/Office365Connect/issues "https://github.com/PhilipHaglund/Office365Connect/issues") \ No newline at end of file From 92d9e9297874272d78d93b34a519ef73fd8c4160 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 21:31:37 +0200 Subject: [PATCH 14/20] Create README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..51595f5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Office365Connect From 6ca84605d2df11b8ffdd30ca72b099cb62dac225 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 22:40:29 +0200 Subject: [PATCH 15/20] Adding Markdown files for the module --- README.md | 33 +++++ ReleaseNotes.md | 1 + docs/Office365Connect.md | 18 +++ docs/en-US/Connect-Office365.md | 147 ++++++++++++++++++++++ docs/en-US/Disconnect-Office365.md | 114 +++++++++++++++++ docs/en-US/about_Office365Connect.help.md | 55 ++------ src/Connect-Office365.ps1 | 20 ++- src/Disconnect-Office365.ps1 | 17 +-- src/Office365Connect.psd1 | 2 +- 9 files changed, 337 insertions(+), 70 deletions(-) create mode 100644 docs/Office365Connect.md create mode 100644 docs/en-US/Connect-Office365.md create mode 100644 docs/en-US/Disconnect-Office365.md diff --git a/README.md b/README.md index 51595f5..ecfe175 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ # Office365Connect + +Office365Connect is a module to connect to all Office 365 services that offer a way in with PowerShell. +The purpose of the module is to easily and quickly connect to all Office 365 services for a tenant using a single credential prompt. Unfortunately Azure MFA is not supported when connecting to the services. + + +## Installation + +If you have the [PowerShellGet](https://msdn.microsoft.com/powershell/gallery/readme) module installed +you can enter the following command: + +```PowerShell +Install-Module Office365Connect -Scope CurrentUser +``` + +Alternatively you can download a ZIP file of the latest version from the [Releases](https://github.com/PhilipHaglund/Office365Connect/releases) +page. + +## Documentation + +You can learn how to use Plaster and write your own templates by reading the documentation: + +- [About Office365Connect](docs/en-US/about_Office365Connect.help.md) +- [Cmdlet Documentation](docs/en-US/Plaster.md) + + +## Maintainers + +- [Philip Haglund](https://github.com/PhilipHaglund) - [@KPHaglund](https://twitter.com/KPHaglund) +- You? + +## License + +This project is [licensed under the MIT License](LICENSE.txt). diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 6d142c6..5c1a49c 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -8,6 +8,7 @@ May 18, 2017 - Use a own repository instead of my general PowerShell [repository](https://github.com/PhilipHaglund/PowerShell "repository"). - New module version order using 3 numbers (1.5.0) instead of four (1.0.1.1). - Add `Exchange Online Protection` as an available service to connect to. +- Fixed some bugs with cached credentials. diff --git a/docs/Office365Connect.md b/docs/Office365Connect.md new file mode 100644 index 0000000..a5529c1 --- /dev/null +++ b/docs/Office365Connect.md @@ -0,0 +1,18 @@ +--- +Module Name: Office365Connect +Module Guid: 80168612-0c36-42bd-9f69-e6a4eeda33d1 +Download Help Link: https://github.com/PhilipHaglund/Office365Connect/tree/master/docs +Help Version: 1.0.0 +Locale: en-US +--- + +# Office365Connect Module +## Description +Office365Connect is a module to connect to all Office 365 services that offer a way in with PowerShell. + +## Office365Connect Cmdlets +### [Connect-Office365](Connect-Office365.md) +Connect to one or more Office 365 services using Powershell. + +### [Disconnect-Office365](Disconnect-Office365.md) +Disconnect from one or more Office 365 services using Powershell. diff --git a/docs/en-US/Connect-Office365.md b/docs/en-US/Connect-Office365.md new file mode 100644 index 0000000..ceffb83 --- /dev/null +++ b/docs/en-US/Connect-Office365.md @@ -0,0 +1,147 @@ +--- +external help file: Office365Connect-help.xml +online version: https://github.com/PhilipHaglund/Office365Connect/ +https://gonjer.com/ +schema: 2.0.0 +--- + +# Connect-Office365 + +## SYNOPSIS +Connect to one or more Office 365 services using Powershell. + +## SYNTAX + +``` +Connect-Office365 [[-Service] ] [-WhatIf] [-Confirm] +``` + +## DESCRIPTION +Connect to one or more Office 365 services using Powershell and a Azure AD account. +Some services requires the installation of separate PowerShell modules or binaries: +AzureAD requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD" +MsolService requires a separate module - http://go.microsoft.com/fwlink/?linkid=236297 +Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 +Skype for Business Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 +Exchange Online, Exchange Online Protection, Complince Center does not require seperate binaries. + +DYNAMIC PARAMETERS +-SharepointDomain \ + Parameter available when the Service parameter contains AllService or SharepointOnline. + The SharepointDomain parameter is necessary when connecting to Sharepoint Online sessions ('https://{0}-admin.sharepoint.com' -f $SharepointDomain). + Example for SharepointDomain can be 'Contoso'. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- +``` +Connect-Office365 +``` + +VERBOSE: Conncting to AzureAD. +VERBOSE: Conncting to MSolService. + +This command connects to AzureAD and MsolService service sessions using the credentials provided when prompted. +AzureAD and MsolService are the default parameter values for the parameter Services. + +### -------------------------- EXAMPLE 2 -------------------------- +``` +Connect-Office365 -Service ComplianceCenter, ExchangeOnline, AzureAD +``` + +VERBOSE: Conncting to AzureAD. +VERBOSE: Conncting to Compliance Center. +VERBOSE: Conncting to Exchange Online. + +This command connects to AzureAD, ComplianceCenter and ExchageOnline service sessions using the credentials provided when prompted. + +### -------------------------- EXAMPLE 3 -------------------------- +``` +Connect-Office365 -Service AzureAD, SharepointOnline +``` + +cmdlet Connect-Office365 at command pipeline position 1 +Supply values for the following parameters: +(Type !? +for Help.) +SharepointDomain: Contoso + +VERBOSE: Conncting to AzureAD. +VERBOSE: Conncting to Sharepoint Online. + +This command connect to AzureAD and SharepointOnline. +SharepointOnline session requires a specified URI when connecting. +In this example, Contoso, is provided at the mandatory prompt parameter, SharepointDomain. + +### -------------------------- EXAMPLE 4 -------------------------- +``` +Connect-Office365 -Service AllServices -SharepointDomain Contoso +``` + +VERBOSE: Connecting to all Office 365 Services. + +This command connects to all Office 365 service sessions using the credentials provided when prompted. +The parameter SharepointDomain is explicit provided to avoid the mandatory parameter prompt. + +## PARAMETERS + +### -Service +Provide one or more Office 365 services to connect to. +Valid values are: +'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: @('AzureAD','MSOnline') +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS + +[https://github.com/PhilipHaglund/Office365Connect/ +https://gonjer.com/](https://github.com/PhilipHaglund/Office365Connect/ +https://gonjer.com/) + diff --git a/docs/en-US/Disconnect-Office365.md b/docs/en-US/Disconnect-Office365.md new file mode 100644 index 0000000..542abc3 --- /dev/null +++ b/docs/en-US/Disconnect-Office365.md @@ -0,0 +1,114 @@ +--- +external help file: Office365Connect-help.xml +online version: https://github.com/PhilipHaglund/Office365Connect/ +https://gonjer.com/ +schema: 2.0.0 +--- + +# Disconnect-Office365 + +## SYNOPSIS +Disconnect from one or more Office 365 services using Powershell. + +## SYNTAX + +``` +Disconnect-Office365 [[-Service] ] [-WhatIf] [-Confirm] +``` + +## DESCRIPTION +Disconnect from one or more Office 365 services using Powershell. +Some services requires the installation of separate PowerShell modules or binaires: +AzureAD requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD" +MsolService requraes a seprate module - http://go.microsoft.com/fwlink/?linkid=236297 +Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 +Skype for Business Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 +Exchange Online, Exchange Online Protection, Complince Center does not require seperate binaries. + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- +``` +Disconnect-Office365 +``` + +VERBOSE: Disconnecting from all Office 365 Services. + +This command disconnects from all Office 365 service sessions that are available and running. + +### -------------------------- EXAMPLE 2 -------------------------- +``` +Disconnect-Office365 -Service ComplianceCenter, ExchangeOnline, AzureAD +``` + +VERBOSE: Disconnecting from AzureAD. +VERBOSE: Azure AD Session is now closed. +VERBOSE: Disconnecting from Compliance Center. +VERBOSE: The Compliance Center Online PSSession is now closed. +VERBOSE: Disconnecting from Exchange Online. +VERBOSE: The Exchange Online PSSession is now closed. + +This command disconnects from AzureAD, Compliance Center and Exchange Online service sessions that are available and running. + +## PARAMETERS + +### -Service +Provide one or more Office 365 services to disconnect from. +Valid values are: +'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: @('AllServices') +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS + +[https://github.com/PhilipHaglund/Office365Connect/ +https://gonjer.com/](https://github.com/PhilipHaglund/Office365Connect/ +https://gonjer.com/) + diff --git a/docs/en-US/about_Office365Connect.help.md b/docs/en-US/about_Office365Connect.help.md index e3e2ba7..5286db4 100644 --- a/docs/en-US/about_Office365Connect.help.md +++ b/docs/en-US/about_Office365Connect.help.md @@ -1,58 +1,29 @@ # Office365Connect ## about_Office365Connect -``` -ABOUT TOPIC NOTE: -The first header of the about topic should be the topic name. -The second header contains the lookup name used by the help system. - -IE: -# Some Help Topic Name -## SomeHelpTopicFileName - -This will be transformed into the text file -as `about_SomeHelpTopicFileName`. -Do not include file extensions. -The second header should have no spaces. -``` - # SHORT DESCRIPTION -{{ Short Description Placeholder }} - -``` -ABOUT TOPIC NOTE: -About topics can be no longer than 80 characters wide when rendered to text. -Any topics greater than 80 characters will be automatically wrapped. -The generated about topic will be encoded UTF-8. -``` +Office365Connect is Powershell Module with two functions to connect and disconnect to all Office 365 services that offers a Powershell-way to connect. # LONG DESCRIPTION -{{ Long Description Placeholder }} +Connect to one or more Office 365 services using the module function `Connect-Office365`. -## Optional Subtopics -{{ Optional Subtopic Placeholder }} +Some Office 365 requires a separate module for binarie to be installed: -# EXAMPLES -{{ Code or descriptive examples of how to leverage the functions described. }} +- **AzureAD** requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet `Install-Module -Name AzureAD`. +- **MsolService** requires a separate module - http://go.microsoft.com/fwlink/?linkid=236297. +- **Sharepoint Online** requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 +- **Skype for Business Online** requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=39366 -# NOTE -{{ Note Placeholder - Additional information that a user needs to know.}} +**Exchange Online**, **Exchange Online Protection**, **Complince Center** does not require seperate binaries. These services uses the built in [PowerShell remote cmdlets](https://technet.microsoft.com/en-us/library/jj984289(v=exchg.160).aspx? "PowerShell remote cmdlets"). -# TROUBLESHOOTING NOTE -{{ Troubleshooting Placeholder - Warns users of bugs}} +*Notice: The links above targeting the modules and binaries may change, please enlight me with an issue if you discover a change.* -{{ Explains behavior that is likely to change with fixes }} -# SEE ALSO -{{ See also placeholder }} +To disconnect from one or more Office 365 services instead of closing the PowerShell session (console) is done with the function `Disconnect-Office365`. -{{ You can also list related articles, blogs, and video URLs. }} -# KEYWORDS -{{List alternate names or titles for this topic that readers might use.}} -- {{ Keyword Placeholder }} -- {{ Keyword Placeholder }} -- {{ Keyword Placeholder }} -- {{ Keyword Placeholder }} +# SEE ALSO +- [https://github.com/PhilipHaglund/Office365Connect](https://github.com/PhilipHaglund/Office365Connect "https://github.com/PhilipHaglund/Office365Connect") +- [https://www.powershellgallery.com/packages/Office365Connect](https://www.powershellgallery.com/packages/Office365Connect "https://www.powershellgallery.com/packages/Office365Connect") diff --git a/src/Connect-Office365.ps1 b/src/Connect-Office365.ps1 index b9c1faf..a9cb622 100644 --- a/src/Connect-Office365.ps1 +++ b/src/Connect-Office365.ps1 @@ -4,7 +4,8 @@ Connect to one or more Office 365 services using Powershell. .DESCRIPTION - Connect to one or more Office 365 (AzureAD) services using Powershell. Some services requires the installation of separate PowerShell modules or binaries. + Connect to one or more Office 365 services using Powershell and a Azure AD account. + Some services requires the installation of separate PowerShell modules or binaries: AzureAD requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD" MsolService requires a separate module - http://go.microsoft.com/fwlink/?linkid=236297 Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 @@ -62,24 +63,19 @@ The parameter SharepointDomain is explicit provided to avoid the mandatory parameter prompt. - .NOTES - Created on: 2017-02-23 14:56 - Created by: Philip Haglund - Organization: Gonjer.com - Version: 1.5.0 - Requirements: Powershell 3.0 - .LINK https://github.com/PhilipHaglund/Office365Connect/ https://gonjer.com/ #> [CmdletBinding( SupportsShouldProcess = $true - )] + )] param( - # Provide one or more Office 365 services to connect to. - # Valid values are: - # 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' + <# + Provide one or more Office 365 services to connect to. + Valid values are: + 'AllServices', 'AzureAD', 'ComplianceCenter', 'ExchangeOnline', 'ExchangeOnlineProtection', 'MSOnline', 'SharepointOnline' ,'SkypeforBusinessOnline' + #> [Parameter( ValueFromPipeline = $true, Position = 0 diff --git a/src/Disconnect-Office365.ps1 b/src/Disconnect-Office365.ps1 index ff1da77..6d45ee0 100644 --- a/src/Disconnect-Office365.ps1 +++ b/src/Disconnect-Office365.ps1 @@ -4,7 +4,8 @@ Disconnect from one or more Office 365 services using Powershell. .DESCRIPTION - Disconnect from one or more Office 365 (AzureAD) services using Powershell. Some services requires the installation of separate PowerShell modules or binaires. + Disconnect from one or more Office 365 services using Powershell. + Some services requires the installation of separate PowerShell modules or binaires: AzureAD requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet "Install-Module -Name AzureAD" MsolService requraes a seprate module - http://go.microsoft.com/fwlink/?linkid=236297 Sharepoint Online requires a separate module - https://www.microsoft.com/en-us/download/details.aspx?id=35588 @@ -16,13 +17,6 @@ VERBOSE: Disconnecting from all Office 365 Services. - VERBOSE: Azure AD Session is now closed. - VERBOSE: MsolService Module is now closed. - VERBOSE: The Compliance Center Online PSSession is now closed. - VERBOSE: The Exchange Online PSSession is now closed. - VERBOSE: The Exchange Online Protection PSSession is now closed. - VERBOSE: The Sharepoint Online Session is now closed. - VERBOSE: The Skype for Business Online PSSession is now closed. This command disconnects from all Office 365 service sessions that are available and running. @@ -39,13 +33,6 @@ This command disconnects from AzureAD, Compliance Center and Exchange Online service sessions that are available and running. - .NOTES - Created on: 2017-02-23 14:56 - Created by: Philip Haglund - Organization: Gonjer.com - Version: 1.5.0 - Requirements: Powershell 3.0 - .LINK https://github.com/PhilipHaglund/Office365Connect/ https://gonjer.com/ diff --git a/src/Office365Connect.psd1 b/src/Office365Connect.psd1 index e0c8c0c..841c97f 100644 --- a/src/Office365Connect.psd1 +++ b/src/Office365Connect.psd1 @@ -27,7 +27,7 @@ CompanyName = 'Gonjer.com' Copyright = '(c) 2017 Philip Haglund. All rights reserved.' # Description of the functionality provided by this module -Description = 'Powershell Module with two functions to connect and disconnect to all Office 365 services.' +Description = 'Office365Connect is a module to connect to all Office 365 services that offer a way in with PowerShell.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '3.0' From 66ff55a9c92369406fe9113ec74331ebed5b90fd Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 22:47:31 +0200 Subject: [PATCH 16/20] Correct typos --- README.md | 9 +++++++-- docs/{ => en-US}/Office365Connect.md | 0 2 files changed, 7 insertions(+), 2 deletions(-) rename docs/{ => en-US}/Office365Connect.md (100%) diff --git a/README.md b/README.md index ecfe175..dd2b211 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ page. ## Documentation -You can learn how to use Plaster and write your own templates by reading the documentation: +You can learn how to use Office365 by reading the documentation: - [About Office365Connect](docs/en-US/about_Office365Connect.help.md) -- [Cmdlet Documentation](docs/en-US/Plaster.md) +- [Cmdlet Documentation](docs/en-US/Office365Connect.md) ## Maintainers @@ -29,6 +29,11 @@ You can learn how to use Plaster and write your own templates by reading the doc - [Philip Haglund](https://github.com/PhilipHaglund) - [@KPHaglund](https://twitter.com/KPHaglund) - You? + +## Contiribute + +- I welcome contributions, issues and forks! + ## License This project is [licensed under the MIT License](LICENSE.txt). diff --git a/docs/Office365Connect.md b/docs/en-US/Office365Connect.md similarity index 100% rename from docs/Office365Connect.md rename to docs/en-US/Office365Connect.md From f41502d8e6161a6329850f92238cfc5bfbb6d985 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 22:53:52 +0200 Subject: [PATCH 17/20] Correct markdown language in cmdlets help --- docs/en-US/Connect-Office365.md | 9 +++++---- docs/en-US/Disconnect-Office365.md | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/en-US/Connect-Office365.md b/docs/en-US/Connect-Office365.md index ceffb83..6d34013 100644 --- a/docs/en-US/Connect-Office365.md +++ b/docs/en-US/Connect-Office365.md @@ -1,10 +1,11 @@ --- -external help file: Office365Connect-help.xml -online version: https://github.com/PhilipHaglund/Office365Connect/ -https://gonjer.com/ -schema: 2.0.0 +Function Name: Connect-Office365 +Download Help Link: https://github.com/PhilipHaglund/Office365Connect/blob/master/docs/en-US/Connect-Office365.md +Help Version: 1.0.0 +Locale: en-US --- + # Connect-Office365 ## SYNOPSIS diff --git a/docs/en-US/Disconnect-Office365.md b/docs/en-US/Disconnect-Office365.md index 542abc3..5db2ed6 100644 --- a/docs/en-US/Disconnect-Office365.md +++ b/docs/en-US/Disconnect-Office365.md @@ -1,8 +1,8 @@ --- -external help file: Office365Connect-help.xml -online version: https://github.com/PhilipHaglund/Office365Connect/ -https://gonjer.com/ -schema: 2.0.0 +Function Name: Connect-Office365 +Download Help Link: https://github.com/PhilipHaglund/Office365Connect/blob/master/docs/en-US/Disconnect-Office365.md +Help Version: 1.0.0 +Locale: en-US --- # Disconnect-Office365 From f048f237f2806bca7de4d1662e9cccdf2804fc24 Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 22:54:38 +0200 Subject: [PATCH 18/20] Correct markdown language in cmdlets help --- docs/en-US/Disconnect-Office365.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en-US/Disconnect-Office365.md b/docs/en-US/Disconnect-Office365.md index 5db2ed6..b792226 100644 --- a/docs/en-US/Disconnect-Office365.md +++ b/docs/en-US/Disconnect-Office365.md @@ -1,5 +1,5 @@ --- -Function Name: Connect-Office365 +Function Name: Disconnect-Office365 Download Help Link: https://github.com/PhilipHaglund/Office365Connect/blob/master/docs/en-US/Disconnect-Office365.md Help Version: 1.0.0 Locale: en-US From 9e96ac7bf82cdd8b2848553b2be697bc1304204c Mon Sep 17 00:00:00 2001 From: Philip Haglund Date: Thu, 18 May 2017 23:01:44 +0200 Subject: [PATCH 19/20] Adding pictures --- docs/pics/Connect-Office365_1.png | Bin 0 -> 22143 bytes docs/pics/Connect-Office365_2.png | Bin 0 -> 11223 bytes docs/pics/Disconnect-Office365_1.png | Bin 0 -> 7371 bytes docs/pics/Office365Connect.jpg | Bin 0 -> 26095 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/pics/Connect-Office365_1.png create mode 100644 docs/pics/Connect-Office365_2.png create mode 100644 docs/pics/Disconnect-Office365_1.png create mode 100644 docs/pics/Office365Connect.jpg diff --git a/docs/pics/Connect-Office365_1.png b/docs/pics/Connect-Office365_1.png new file mode 100644 index 0000000000000000000000000000000000000000..a6aa05019acfa1637c1fb729bef326506789e2ee GIT binary patch literal 22143 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 Date: Thu, 18 May 2017 23:04:43 +0200 Subject: [PATCH 20/20] Adding pictures to about Help --- docs/en-US/about_Office365Connect.help.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/en-US/about_Office365Connect.help.md b/docs/en-US/about_Office365Connect.help.md index 5286db4..52e6c19 100644 --- a/docs/en-US/about_Office365Connect.help.md +++ b/docs/en-US/about_Office365Connect.help.md @@ -7,6 +7,10 @@ Office365Connect is Powershell Module with two functions to connect and disconne # LONG DESCRIPTION Connect to one or more Office 365 services using the module function `Connect-Office365`. +![Connect-Office365](https://raw.githubusercontent.com/PhilipHaglund/Office365Connect/dev/docs/pics/Connect-Office365_1.png) + +![Get-AzureADUser](https://raw.githubusercontent.com/PhilipHaglund/Office365Connect/dev/docs/pics/Connect-Office365_2.png) + Some Office 365 requires a separate module for binarie to be installed: - **AzureAD** requires a separate module - https://www.powershellgallery.com/packages/AzureAD/ or cmdlet `Install-Module -Name AzureAD`. @@ -21,6 +25,8 @@ Some Office 365 requires a separate module for binarie to be installed: To disconnect from one or more Office 365 services instead of closing the PowerShell session (console) is done with the function `Disconnect-Office365`. +![](https://raw.githubusercontent.com/PhilipHaglund/Office365Connect/dev/docs/pics/Disconnect-Office365_1.png) +