SnsPsModule.psm1



##### Add-SnsSecurityPolicy =======================================================
Function Add-SnsSecurityPolicy ()
{
<#
.SYNOPSIS
This CmdLet Sets The PowerShell Host And Defines The SnsPsModule Module Configuration
.DESCRIPTION
This CmdLet Sets The PowerShell Host And Defines The SnsPsModule Module Configuration
--The CmdLet Sets The PowerShell Session To Trust All Certificates Without Revocation Validation
--The CmdLet Adds Support Of TLS12 Security Protocol To The PowerShell Session
The Default Protocols Are Not Removed From The List To Allow Other Connections In The Same PowerShell Session
--The CmdLet Generates The SnsPsModule Module Configuration Variable Required For The Remaining CmdLets In This Module
.INPUTS
No Inputs
.OUTPUTS
Global Variable [System.Object]$global:SnsModuleCfg - Contains The SnsPsModule Module Configuration
.NOTES
VERSION INFORMATION:
- Added Trust All Certificates Without Revocation And Root Trust Checks
- Added The TLS12 Security Protocol Support
- Added HTML Invalid Characters Configuration
- Added Transformation Entry InputField OutputField And MatchType Translation Configuration
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
Add-SnsSecurityPolicy;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding()]
Param()
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        #==================================================================================
        #region PowerShell Version Verification
        #==================================================================================
        
        If ([System.Int32]"$($Host.Version.Major)" -lt 5)
        {
            Write-Warning "The PowerShell Version Is Less Than 5. The Module Might Not Work Properly. Use It At Your Own Risk.";
        }
        
        #==================================================================================
        #endregion PowerShell Version Verification
        #==================================================================================
        
        #==================================================================================
        #region Import Required Modules
        #==================================================================================
        
        ##### Import The DNS Module
        If ( `
            (-not (Get-Module -Name "Microsoft.PowerShell.Utility" -Verbose:$false -Debug:$false)) `
            -and `
            (-not -not (Get-Module -Name "Microsoft.PowerShell.Utility" -ListAvailable:$true -Verbose:$false -Debug:$false)) `
        )
        {
            Write-Host "Import Microsoft.PowerShell.Utility PowerShell Module" -ForegroundColor "Green";
            [System.String]$strVerbosePreference = "$($VerbosePreference)"; $VerbosePreference = "SilentlyContinue";
            Import-Module -Name "Microsoft.PowerShell.Utility" -Global:$true -Force:$true -Debug:$false;
            $VerbosePreference = "$($strVerbosePreference)";
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strVerbosePreference";
        }
        
        #==================================================================================
        #endregion Import Required Modules
        #==================================================================================
        
        #==================================================================================
        #region Add TLS12 Protocol
        #==================================================================================
        
        ##### Set The Security Protocol
        Write-Debug "SecurityProtocol Verification and Setting.";
        If ("$([Net.ServicePointManager]::SecurityProtocol)" -notlike "*Tls12*")
        {
            Write-Verbose "`r`n`r`nAdding All SecurityProtocols`r`n`r`n";
            [Net.ServicePointManager]::SecurityProtocol = `
            @(
                #[Net.SecurityProtocolType]::Tls13,
                [Net.SecurityProtocolType]::Tls12,
                [Net.SecurityProtocolType]::Tls11,
                [Net.SecurityProtocolType]::Tls,
                [Net.SecurityProtocolType]::Ssl3
            );
        }
        
        #==================================================================================
        #endregion Add TLS12 Protocol
        #==================================================================================
        
        #==================================================================================
        #region Trust All Certificates Policy
        #==================================================================================
        
        ##### Set Trust All Certs Policy
        Write-Debug "Continue With CertificatePolicy Verification and Setting.";
        If ($false -and ("$([System.Net.ServicePointManager]::CertificatePolicy)" -notlike "TrustAllCertsPolicy"))
        {
            ##### Create The CertificatePolicy Object Using C#
            Write-Verbose "`r`n`r`nAdding TrustAllCertsPolicy`r`n`r`n";
            Add-Type `
@"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy
{
    public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
    {
        return true;
    }
}
"@

            
            ##### Set The CertificatePolicy To Trust All Certificates
            [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy;
        }
        
        #==================================================================================
        #endregion Trust All Certificates Policy
        #==================================================================================
        
        #==================================================================================
        #region Skip Certificate Validation
        #==================================================================================
        
        If ($false -and (-not ([System.Management.Automation.PSTypeName]"ServerCertificateValidationCallback").Type))
        {
            Add-Type -TypeDefinition @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public class ServerCertificateValidationCallback
{
    public static void Ignore()
    {
        if(ServicePointManager.ServerCertificateValidationCallback == null)
        {
            ServicePointManager.ServerCertificateValidationCallback += delegate(Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; };
        }
    }
}
"@

            [ServerCertificateValidationCallback]::Ignore();
        }
        
        #==================================================================================
        #endregion Skip Certificate Validation
        #==================================================================================
        
        #==================================================================================
        #region Generate Module Configuration Variable
        #==================================================================================
        
        ##### Generate A Custom Object
        [System.Object]$objObject = New-Object -TypeName "System.Object" -Verbose:$false -Debug:$false;
        
        If ($true)
        {
            ##### Generate The PowerShell Module File SHA256 Hash
            $objObject | Add-Member -Force:$true -MemberType "NoteProperty" -Name "ModuleHash" -Value "$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"").Hash)";
        }
        
        If ($true)
        {
            ##### Generate The System.Globalization.CultureInfo.TextInfo Object For "en-Us" Culture
            $objObject | Add-Member -Force:$true -MemberType "NoteProperty" -Name "TextInfo" -Value (([System.Globalization.CultureInfo]::GetCultureInfo("en-Us")).TextInfo);
        }
        
        If ($true)
        {
            ##### Get The Process Owner
            [System.String]$strPrOwner = "$(((Get-WmiObject -Class ""Win32_Process"" -Verbose:$false -Debug:$false | Where-Object {""$($_.ProcessId)"" -eq ""$($pid)""} -Verbose:$false -Debug:$false).GetOwner()).User)";
            
            ##### Add The Process Owner To The Configuration Object
            $objObject | Add-Member -Force:$true -MemberType "NoteProperty" -Name "ProcessOwner" -Value "$($strPrOwner)";
            
            ##### Reset The Variable
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strPrOwner";
        }
        
        If ($true)
        {
            ##### Add The Configuration Hash As A Property Of The Master Configuration Object
            $objObject | Add-Member -Force:$true -MemberType "NoteProperty" -Name "HtmlIvalidChars" -Value ([Ordered]@{});
            
            #### Add The Invalid Characters / Escaped Characters Pairs
            $objObject.HtmlIvalidChars.Add(' ', '%20');
            $objObject.HtmlIvalidChars.Add('"', '%22');
            $objObject.HtmlIvalidChars.Add('+', '%2B');
            $objObject.HtmlIvalidChars.Add('&', '%26');
        }
        
        ##### Generate The Master Configuration Variable
        If (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object {"$($_.Name)" -like "SnsModuleCfg"} -Verbose:$false -Debug:$false))
        {New-Variable -Scope "Global" -Option "Constant" -Name "SnsModuleCfg" -Value ($objObject)};
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objObject";
        
        #==================================================================================
        #endregion Generate Module Configuration Variable
        #==================================================================================
    }
}
Add-SnsSecurityPolicy -Verbose:$false -Debug:$false -WarningAction "$($WarningPreference)" -ErrorAction "$($ErrorActionPreference)";

#==================================================================================
#region Modify Host Appearance
#==================================================================================

((Get-Host).UI.RawUI).WindowTitle = "SnsPsModule - Created by Svetoslav Savov";
((Get-Host).UI.RawUI).BufferSize.Width = 120;
((Get-Host).UI.RawUI).BufferSize.Height = 50;
((Get-Host).UI.RawUI).WindowSize.Width = 120;
((Get-Host).UI.RawUI).WindowSize.Height = 50;
((Get-Host).UI.RawUI).MaxWindowSize.Width = 120;
((Get-Host).UI.RawUI).MaxWindowSize.Height = 50;

If (([System.Environment]::UserInteractive) -and (-not -not "$($ExecutionContext.Host.IsRunspacePushed)"))
{
    ##### Get The Latest Version Of The Module From PSGallery
    Write-Debug "Get The Latest Version Of The Module From PSGallery";
    [System.Object]$objPsGalMdl = $null;
    Try
    {
        [System.Object]$objPsGalMdl = Find-Module -Name "SnsPsModule" -Repository "PSGallery" -ErrorAction "Stop";
    } Catch {};
    
    ##### Get The Locally Installed Module
    Write-Debug "Get The Locally Installed Module";
    [System.Array]$arrLocMdl = @();
    [System.Array]$arrLocMdl = Get-Module -Name "SnsPsModule" -ListAvailable;
    
    ##### Check Whether SnsPsModule Is The Latest Version
    Write-Debug "Check Whether SnsPsModule Is The Latest Version";
    If ((-not -not "$($objPsGalMdl.Version)") -and ("$($arrLocMdl[0].Version)" -ne "$($objPsGalMdl.Version)"))
    { Write-Warning "Module ""SnsPsModule"" Have Newer Version. Please Update It."; }
    
    If (-not "$($MyInvocation.PSCommandPath)")
    {
        [System.Text.StringBuilder]$strMsg = New-Object -TypeName "System.Text.StringBuilder" -Verbose:$false -Debug:$false -ArgumentList @(150);
        [void]$strMsg.Append("`r`n$('*' * 100)`r`n*$(' ' * 98)*`r`n");
        [void]$strMsg.Append("*$(' ' * 33)SnsPsModule - PowerShell Module$(' ' * 34)*`r`n");
        [void]$strMsg.Append("*$(' ' * 37)ModuleVersion - 1.0.1.3$(' ' * 38)*`r`n*$(' ' * 98)*`r`n");
        [void]$strMsg.Append("*$(' ' * 9)AUTHOR: Svetoslav Nedyalkov Savov$(' ' * 53)*`r`n");
        [void]$strMsg.Append("*$(' ' * 9)THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK$(' ' * 9)*`r`n");
        [void]$strMsg.Append("*$(' ' * 9)OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.$(' ' * 15)*`r`n");
        [void]$strMsg.Append("*$(' ' * 98)*`r`n$('*' * 100)`r`n`r`n");
        Write-Host "$($strMsg.ToString())" -ForegroundColor 'Green';
        Remove-Variable -Force -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strMsg";
    }
}

#==================================================================================
#endregion Modify Host Appearance
#==================================================================================

#==================================================================================
#region Helper Comands
#==================================================================================

##### Export-SnsCredentialFileAsServiceHelper =====================================
Function Export-SnsCredentialFileAsServiceHelper ()
{
<#
.SYNOPSIS
This CmdLet Creates Encrypted Password File
.DESCRIPTION
This CmdLet Creates Encrypted Password File
The CmdLet Is Intended To Prepare Credential File Of Other Accounts When It Is Run As A Service
While The Password Is Encrypted Securely And Cannot Be Decrypted By Other Accounts Than The One Who Encrypted It,
The Process For Creation Of The Encrypted File Is Not The Most Secure One
This CmdLet Is Used Mainly When Automatic Scripts Will Need Other Credentials And Those Scripts Will Be Executed
With Service Accounts That Have Interactive Logon Denied
Active Logon Denied Makes Impossible The Usage Of The Export-SnsCredentialFile CmdLet Who Require The Credentials
Interactively And The Password Is Not Provided In Clear Text
The Produced Credential File Contains Information About When It Was Created And Who Created It
WARNING: The Produced Encrypted Credential Files Can Be Decrypted Only Within The Security Context In Which They
Are Created And Only On The Machine They Are Created
With Other Words Only The Person Who Have Created A File Can Decrypt It On The Same Machine Only.
WARNING: In Order To Create The Encrypted Password File This Function Will Require The Password To Be Provided As
Clear Text To CmdLet Parameter
There Is Increased Risk The Password To Be Intercepted In The Process Of Providing It To The CmdLet.
.PARAMETER UserName
Specifies The UserName Of The Credentials Which Have To Be Encrypted.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Password
Specifies The Password Of The Credentials Which Have To Be Encrypted.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Have To Be Exported.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: Folder Existence Validation
.INPUTS
The CmdLet Does Not Support Pipeline Input.
.OUTPUTS
Encrypted Secure Password File.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
Export-SnsCredentialFileAsServiceHelper -UserName 'Contoso\User01' -Password 'Pa$$w0rd' -FolderPath 'C:';
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$Password,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({[System.IO.Directory]::Exists("$($_)")})]
    [System.String]$FolderPath
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Initialize The Variables
        [System.Security.SecureString]$strSecurePass = $null;
        [System.String]$strEncryptedPass = "";
        [System.String]$strPath = "";
        
        ##### Enumerate The Secure Password String
        Write-Debug "Enumerate The Secure Password String";
        [System.Security.SecureString]$strSecurePass = $null;
        [System.Security.SecureString]$strSecurePass = "$($Password)" | ConvertTo-SecureString -AsPlainText:$true -Force:$true -Debug:$false -Verbose:$false;
        
        ##### Convert The Secure Password String To Encrypted Password String
        Write-Debug "Convert The Secure Password String To Encrypted Password String";
        [System.String]$strEncryptedPass = "";
        [System.String]$strEncryptedPass = $strSecurePass | ConvertFrom-SecureString -Verbose:$false -Debug:$false;
        
        ##### Enumerate The Encrypted File Path
        Write-Debug "Enumerate The Encrypted File Path";
        [System.String]$strPath = "";
        [System.String]$strPath = "$($FolderPath.Trim(""\""))\$(""$($UserName)"".Replace(""\"",""@@"")).ini";
        If ([SnsPsModule.SnsCredentialFile]::Export("$($strEncryptedPass)", "$($strPath)"))
        {
            ##### Create The Confirmation File
            Write-Debug "Create The Confirmation File";
            [System.String]$strPath = "";
            [System.String]$strPath = "$($FolderPath.Trim('\'))\!!! Password_Export_OK !!!.txt";
            "$([System.DateTime]::Now.ToString(""yyyy-MM-dd HH:mm:ss""))" | `
                Set-Content -Path "$($strPath)" -Encoding 'Unicode' -Force:$true -Confirm:$false -PassThru:$false -Debug:$false -Verbose:$false;
            #####
        }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strPath";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEncryptedPass";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strSecurePass";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "FolderPath";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "Password";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "UserName";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        [System.GC]::Collect(); Exit 0;
        Stop-Process -Id $pid -Force:$true -Confirm:$false -Verbose:$false -Debug:$false;
    }
}

##### Get-SnsNearestAliveCasServerHelper ==========================================
Function Get-SnsNearestAliveCasServerHelper ()
{
<#
.SYNOPSIS
Enumerates The Nearest Up And Running Exchange CAS Server.
.DESCRIPTION
Enumerates The Nearest Up And Running Exchange CAS Server.
This Is A Helper Function And Won't Be Exported / Available To The User.
.INPUTS
Pipeline Input None
.OUTPUTS
[System.String] Which The FQDN Of The Enumerated CAS Server.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.String]$HostName = Get-SnsNearestAliveCasServerHelper -Verbose:$bolVerbose -Debug:$false;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding()]
Param ()
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Verify Whether The CmdLet Is Executed In Active Directory Domain
        ##### On Premises Exchange Can Live Only In Active Directory Environment
        Write-Debug "Verify Whether The CmdLet Is Executed In Active Directory Domain";
        [System.String]$strHostName = "";
        If (-not -not "$(([ADSI]"""").distinguishedName)")
        {
            ##### Generate The Ping Objects
            Write-Debug "Generate The Ping Objects";
            [System.Net.NetworkInformation.Ping]$objPing = $null;
            [System.Net.NetworkInformation.Ping]$objPing = New-Object -TypeName 'System.Net.NetworkInformation.Ping' -Verbose:$false -Debug:$false;
            
            ##### Retrieve CAS Servers HostName From AD
            Write-Debug "Retrieve CAS Servers HostName From AD";
            [System.Array]$arrCasSrvs = @();
            [System.Array]$arrCasSrvs = Search-SnsAdObject -DomainController "$(([ADSI]""LDAP://RootDSE"").dnsHostName)" -ReturnProperties @("serviceDNSName") `
                -LdapQuery "(&(objectClass=serviceConnectionPoint)(objectCategory=CN=Service-Connection-Point,$(([ADSI]""LDAP://RootDSE"").schemaNamingContext))(serviceClassName=ms-Exchange-AutoDiscover-Service))" `
                -SearchRoot "$(([ADSI]""LDAP://RootDSE"").configurationNamingContext)" -Verbose:$false -Debug:$false;
            #####
            
            ##### Ping All CAS Servers And Take The Nearest One
            Write-Debug "Ping All CAS Servers And Take The Nearest One";
            [System.String]$strHostName = $arrCasSrvs | Select-Object -Verbose:$false -Debug:$false `
                -Property @{ "n" = "Fqdn"; "e" = { "$($_.Properties.servicednsname).$(""$($_.Properties.adspath)"".Substring(""$($_.Properties.adspath)"".IndexOf(""DC="")).Replace(""DC="", """").Replace("","", "".""))" } } | `
                Select-Object -Verbose:$false -Debug:$false `
                -Property  @( "Fqdn", @{ "n" = "TTL"; "e" = { [System.Int32]"$($objPing.Send(""$($_.Fqdn)"", 1000, [System.Text.Encoding]::ASCII.GetBytes(""S"")).Options.Ttl)" } } ) | `
                Sort-Object -Property @( "Ttl", "Fqdn" ) -Descending:$true -Verbose:$false -Debug:$false | `
                Select-Object -First 1 -Verbose:$false -Debug:$false | `
                Select-Object -ExpandProperty "Fqdn" -Verbose:$false -Debug:$false;
            #####
            
            Write-Verbose "Dynamically Enumerated CAS Server: ""$($strHostName)""";
            
            ##### Reset The Variables
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrCasSrvs";
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objPing";
        }
        
        Return $strHostName;
    }
}

##### Import-SnsModuleHelper ======================================================
Function Import-SnsModuleHelper ()
{
<#
.SYNOPSIS
Imports The Specified PowerShell Module Into The PowerShell Session.
.DESCRIPTION
Imports The Specified PowerShell Module Into The PowerShell Session.
This Is A Helper Function And Won't Be Exported / Available To The User.
.PARAMETER Module
Specifies The Module Name Or Module Path.
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Boolean] Which Indicates Whether The Import Failed.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
If (Import-SnsModuleHelper -Module "$($ModuleName)" -EventSource "$($EventSource)") { Return; }
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.String]$Module,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource = ""
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Enumerate The Module Name
        Write-Debug "Enumerate The Module Name";
        [System.String]$strModuleName = "$(""$($Module)"".Split(""\"")[-1].Replace("".psd1"", """").Replace("".psm1"", """").Replace("".dll"", """"))";
        
        ##### Verify AzureADPreview PowerShell Module Existence
        Write-Debug "Verify $($strModuleName) PowerShell Module Existence";
        If (-not (Get-Module -Name "$($Module)" -ListAvailable:$true -Verbose:$false -Debug:$false))
        {
            [System.String]$strEventMessage = "PowerShell Module $($strModuleName) Is Not Installed.";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return $true;
        }
        
        ##### Load AzureADPreview PowerShell Module
        Write-Debug "Load $($strModuleName) PowerShell Module";
        If ((-not (Get-Module -Name "$($strModuleName)" -Verbose:$false -Debug:$false)) -and (-not -not (Get-Module -Name "$($Module)" -ListAvailable:$true -Verbose:$false -Debug:$false)))
        {
            Write-Host "Import $($strModuleName) Module" -ForegroundColor "Green";
            [System.String]$strVerbosePreference = "$($VerbosePreference)"; $VerbosePreference = "SilentlyContinue";
            Import-Module -Name "$($Module)" -Global:$true -Force:$true -Verbose:$false -Debug:$false;
            $VerbosePreference = "$($strVerbosePreference)";
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strVerbosePreference";
        }
        
        ##### Verify AzureADPreview PowerShell Module Load
        Write-Debug "Verify $($strModuleName) PowerShell Module Load";
        If (-not (Get-Module -Name "$($strModuleName)" -Verbose:$false -Debug:$false))
        {
            [System.String]$strEventMessage = "Failed To Load PowerShell Module $($strModuleName)";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return $true;
        }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strModuleName";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Pass The Output Object To The Pipeline
        Write-Debug "Pass Output Object To The Pipeline";
        Return $false;
    }
}

##### Log-SnsEventLogMessageHelper ================================================
Function Log-SnsEventLogMessageHelper ()
{
<#
.SYNOPSIS
This Helper CmdLet Simplifies Logging Of Events In The Console And Windows Event Viewer Application Log.
.DESCRIPTION
This Helper CmdLet Simplifies Logging Of Events In The Console And Windows Event Viewer Application Log.
For Logging Into Event Viewer Is Required To Be Specified Existing EventSource.
.PARAMETER Message
Specifies The Event Message.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: Using Existence Validation And Syntax Validation.
.PARAMETER EventLogEntryType
Specifies The Event Type / Severity.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: Using ValidateSet Validation
.PARAMETER EventSource
Specifies The EventSource.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER TerminatingError
Specifies Whether An Error Would Be Terminating One And Exist The PowerShell Session With Code 1.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
The CmdLet Does Not Support Pipeline Input.
.OUTPUTS
The CmdLet Does Not Have Output.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
Log-SnsEventLogMessageHelper -Message "Test Message" -EventLogEntryType "Error" -EventSource "SnsPsModule";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.String]$Message,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateSet("Error", "Warning", "Information")]
    [ValidateNotNullOrEmpty()][System.String]$EventLogEntryType,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$TerminatingError = $true
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Verify The EventLogEntryType
        Write-Debug "Verify The EventLogEntryType";
        Switch ("$($EventLogEntryType)")
        {
            "Error" { Write-Error "$($Message)" -ErrorAction "Continue"; Break; }
            "Warning" { Write-Warning "$($Message)" -WarningAction "Continue"; Break; }
            "Information" { Write-Host "$($Message)" -ForegroundColor "Green"; Break; }
            default { Write-Error "Unknown EventLogEntryType: ""$($EventLogEntryType)""." -ErrorAction "Stop"; }
        }
        
        ##### Verify Whether The EventSource Exists
        Write-Debug "Verify Whether The EventSource Exists";
        If ((-not -not "$($EventSource)") -and [System.Diagnostics.EventLog]::SourceExists("$($EventSource)"))
        {
            [SnsPsModule.SnsEventLog]::WriteSnsEventLog("$($EventSource)", "$($Message)", "$($EventLogEntryType)", 9998);
            
            If (("$($EventLogEntryType)" -eq "Error") -and $TerminatingError.IsPresent)
            {
                Start-Sleep -Seconds 60 -Verbose:$false -Debug:$false; Exit 1;
                Stop-Process -Id $pid -Force:$true -Confirm:$false -Verbose:$false -Debug:$false;
            }
        }
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### New-SnsExoSessionInteractiveHelper ==========================================
Function New-SnsExoSessionInteractiveHelper ()
{
<#
.SYNOPSIS
This CmdLet Establish Interactive Remote PowerShell Session To ExchangeOnline Using ExchangeOnlineManagement
PowerShell Module.
.DESCRIPTION
This CmdLet Establish Interactive Remote PowerShell Session To ExchangeOnline Using ExchangeOnlineManagement
PowerShell Module.
This Is A Helper Function Not Exported To The End User Session.
.PARAMETER Prefix
Specifies The Prefix For The CmdLets In This Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER ProxyAccessType
Specifies The ProxyAccessType.
The Best Practice Require Direct Internet Access To Office 365.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Management.Automation.PSModuleInfo] Which Contains The Imported Via The New PSSession Module.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsExoSessionInteractiveHelper `
-Prefix "$($Prefix)" -Attempts $Attempts -ProxyAccessType "$($ProxyAccessType)";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowNull()][AllowEmptyString()][System.String]$Prefix,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$ProxyAccessType,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.Int32]$Attempts
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Initialize The Variables
        [System.Array]$arrTmp = @();
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        [System.Int32]$intI = 0;
        [System.Management.Automation.PSModuleInfo[]]$arrSesModule = @();
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        
        ##### Get Any Previous Temporary Modules
        Write-Debug "Get Any Previous Temporary Modules";
        [System.String[]]$arrTmp = @();
        [System.String[]]$arrTmp = Get-Module -Verbose:$false -Debug:$false | `
            Select-Object -Verbose:$false -Debug:$false -ExpandProperty "Name" | `
            Where-Object -Verbose:$false -Debug:$false {"$($_)".StartsWith("tmp")};
        #####
        
        ##### Generate The Connect-ExchangeOnline Splatting HashTable
        Write-Debug "Generate The Connect-ExchangeOnline Splatting HashTable";
        $hshSplat.Add("PSSessionOption", $(New-PSSessionOption -Verbose:$false -Debug:$false -SkipRevocationCheck:$true -SkipCACheck:$true -SkipCNCheck:$true -ProxyAccessType "$($ProxyAccessType)"));
        $hshSplat.Add("ShowBanner", $false);
        $hshSplat.Add("Verbose", $false);
        $hshSplat.Add("Debug", $false);
        $hshSplat.Add("InformationAction", "SilentlyContinue");
        $hshSplat.Add("ErrorAction", "SilentlyContinue");
        $hshSplat.Add("WarningAction", "SilentlyContinue");
        
        ##### Verify Whether Prefix Is Specified
        Write-Debug "Verify Whether Prefix Is Specified";
        If (-not -not "$($Prefix)") { $hshSplat.Add("Prefix", "$($Prefix)"); }
        
        ##### Loop The Session Creation
        Write-Debug "Loop The Session Creation";
        [System.Int32]$intI = 0;
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        Do
        {
            ##### Establish The ExchangeOnline Session
            Write-Verbose "Establish The ExchangeOnline Session";
            Connect-ExchangeOnline @hshSplat;
            
            ##### Enumerate The Session Modules
            Write-Debug "Enumerate The Session Modules"
            [System.Management.Automation.PSModuleInfo[]]$arrSesModule = @();
            [System.Management.Automation.PSModuleInfo[]]$arrSesModule = Get-Module -Verbose:$false -Debug:$false | `
                Where-Object -Verbose:$false -Debug:$false {"$($_.Name)".StartsWith("tmp")} | `
                Where-Object -Verbose:$false -Debug:$false {$arrTmp -inotcontains "$($_.Name)"};
            #####
            
            ##### Verify Whether There Are No More Than One Module Enumerated
            Write-Debug "Verify Whether There Are No More Than One Module Enumerated";
            If ($arrSesModule.Count -gt 1)
            {
                ##### Filter Out The Non Exchange Online Temp Modules
                [System.Management.Automation.PSModuleInfo[]]$arrSesModule = $arrSesModule | `
                    Where-Object -Verbose:$false -Debug:$false {$_.ExportedCommands.Keys -icontains "Get-$($Prefix)AcceptedDomain"};
                #####
            }
            
            ##### Assign The Newly Created Module To The Verification Object
            Write-Debug "Assign The Newly Created Module To The Verification Object";
            [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
            If ($arrSesModule.Count -gt 0) { [System.Management.Automation.PSModuleInfo]$objSesModule = $arrSesModule[0]; }
            
            ##### Process The Loop Variable And TimeOut
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
        }
        While ((-not "$($objSesModule.Name)") -and ($intI -lt $Attempts))
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrSesModule";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplat";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrTmp";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        Return $objSesModule;
    }
}

##### New-SnsExoSessionMsModuleHelper =============================================
Function New-SnsExoSessionMsModuleHelper ()
{
<#
.SYNOPSIS
This CmdLet Establish Remote PowerShell Session To ExchangeOnline Using Certificate Thumbprint And
ExchangeOnlineManagement PowerShell Module.
.DESCRIPTION
This CmdLet Establish Remote PowerShell Session To ExchangeOnline Using Certificate Thumbprint And
ExchangeOnlineManagement PowerShell Module.
This Is A Helper Function Not Exported To The End User Session.
.PARAMETER CertificateThumbprint
Specifies The Thumbprint Of A Certificate Used To Authenticate As Azure ServicePrincipal.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER ApplicationId
Specifies The ApplicationId Of Azure ServicePrincipal.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Organization
Specifies The Organization Name.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Prefix
Specifies The Prefix For The CmdLets In This Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER ProxyAccessType
Specifies The ProxyAccessType.
The Best Practice Require Direct Internet Access To Office 365.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Management.Automation.PSModuleInfo] Which Contains The Imported Via The New PSSession Module.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsExoSessionMsModuleHelper `
-Organization "contoso.onmicrosoft.com" -CertificateThumbprint "012THISISADEMOTHUMBPRINT" `
-ApplicationId "00000000-0000-0000-0000-000000000000" -Prefix "$($Prefix)" -Attempts $Attempts;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$CertificateThumbprint,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$ApplicationId,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$Organization,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowNull()][AllowEmptyString()][System.String]$Prefix,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$ProxyAccessType,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.Int32]$Attempts
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Initialize The Variables
        [System.Array]$arrTmp = @();
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        [System.Int32]$intI = 0;
        [System.Management.Automation.PSModuleInfo[]]$arrSesModule = @();
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        
        ##### Get Any Previous Temporary Modules
        Write-Debug "Get Any Previous Temporary Modules";
        [System.String[]]$arrTmp = @();
        [System.String[]]$arrTmp = Get-Module -Verbose:$false -Debug:$false | `
            Select-Object -Verbose:$false -Debug:$false -ExpandProperty "Name" | `
            Where-Object -Verbose:$false -Debug:$false {"$($_)".StartsWith("tmp")};
        #####
        
        ##### Generate The Connect-ExchangeOnline Splatting HashTable
        Write-Debug "Generate The Connect-ExchangeOnline Splatting HashTable";
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        $hshSplat.Add("CertificateThumbprint", "$($CertificateThumbprint)");
        $hshSplat.Add("Organization", "$($Organization)");
        $hshSplat.Add("AppId", "$($ApplicationId)");
        $hshSplat.Add("PSSessionOption", $(New-PSSessionOption -Verbose:$false -Debug:$false -SkipRevocationCheck:$true -SkipCACheck:$true -SkipCNCheck:$true -ProxyAccessType "$($ProxyAccessType)"));
        $hshSplat.Add("ShowBanner", $false);
        $hshSplat.Add("Verbose", $false);
        $hshSplat.Add("Debug", $false);
        $hshSplat.Add("InformationAction", "SilentlyContinue");
        $hshSplat.Add("ErrorAction", "SilentlyContinue");
        $hshSplat.Add("WarningAction", "SilentlyContinue");
        
        ##### Verify Whether Prefix Is Specified
        Write-Debug "Verify Whether Prefix Is Specified";
        If (-not -not "$($Prefix)") { $hshSplat.Add("Prefix", "$($Prefix)"); }
        
        ##### Loop The Session Creation
        Write-Debug "Loop The Session Creation";
        [System.Int32]$intI = 0;
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        Do
        {
            ##### Establish The ExchangeOnline Session
            Write-Verbose "Establish The ExchangeOnline Session";
            Connect-ExchangeOnline @hshSplat;
            
            ##### Enumerate The Session Modules
            Write-Debug "Enumerate The Session Modules"
            [System.Management.Automation.PSModuleInfo[]]$arrSesModule = @();
            [System.Management.Automation.PSModuleInfo[]]$arrSesModule = Get-Module -Verbose:$false -Debug:$false | `
                Where-Object -Verbose:$false -Debug:$false {"$($_.Name)".StartsWith("tmp")} | `
                Where-Object -Verbose:$false -Debug:$false {$arrTmp -inotcontains "$($_.Name)"};
            #####
            
            ##### Verify Whether There Are No More Than One Module Enumerated
            Write-Debug "Verify Whether There Are No More Than One Module Enumerated";
            If ($arrSesModule.Count -gt 1)
            {
                ##### Filter Out The Non Exchange Online Temp Modules
                [System.Management.Automation.PSModuleInfo[]]$arrSesModule = $arrSesModule | `
                    Where-Object -Verbose:$false -Debug:$false {$_.ExportedCommands.Keys -icontains "Get-$($Prefix)AcceptedDomain"};
                #####
            }
            
            ##### Assign The Newly Created Module To The Verification Object
            Write-Debug "Assign The Newly Created Module To The Verification Object";
            [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
            If ($arrSesModule.Count -gt 0) { [System.Management.Automation.PSModuleInfo]$objSesModule = $arrSesModule[0]; }
            
            ##### Process The Loop Variable And TimeOut
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
        }
        While ((-not "$($objSesModule.Name)") -and ($intI -lt $Attempts))
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrSesModule";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplat";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrTmp";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        Return $objSesModule;
    }
}

##### New-SnsRemoteSessionNoModuleHelper ==========================================
Function New-SnsRemoteSessionNoModuleHelper ()
{
<#
.SYNOPSIS
This CmdLet Establish Remote PowerShell Session To ExchangeOnline Using Credential Object Without External
PowerShell Modules.
.DESCRIPTION
This CmdLet Establish Remote PowerShell Session To ExchangeOnline Using Credential Object Without External
PowerShell Modules.
This Is A Helper Function Not Exported To The End User Session.
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Url
Specifies The Destination URL For The Remote Required Remote Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Prefix
Specifies The Prefix For The CmdLets In This Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER ProxyAccessType
Specifies The ProxyAccessType.
The Best Practice Require Direct Internet Access To Office 365.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Management.Automation.PSModuleInfo] Which Contains The Imported Via The New PSSession Module.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsRemoteSessionNoModuleHelper `
-Credential $objCredential -Url "$($Url)" -Prefix "$($Prefix)" `
-ProxyAccessType "$($ProxyAccessType)" -Attempts $Attempts -EventSource "$($EventSource)";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$Url,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowNull()][AllowEmptyString()][System.String]$Prefix,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.String]$ProxyAccessType,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.Int32]$Attempts,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Initialize The Variables
        [System.Management.Automation.Runspaces.PSSession]$objPsSession = $null;
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        [System.Int32]$intI = 0;
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        [System.String]$strSessionName = "$(""$($Url)"".Split(""/"")[2].Replace(""ps.outlook.com"", ""ExchangeOnlineInternalSession""))";
        
        #==================================================================================
        #region Create Remote PSSession Session
        #==================================================================================
        
        ##### Verify The Prerequisites For Remote PSSession
        Write-Debug "Verify The Prerequisites For Remote PSSession";
        [System.Management.Automation.Runspaces.PSSession]$objPsSession = $null;
        If (-not -not "$($Credential.UserName)")
        {
            ##### Generate The New-PSSession Splatting HashTable
            Write-Debug "Generate The New-PSSession Splatting HashTable";
            [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
            $hshSplat.Add("Name", "$($strSessionName)");
            $hshSplat.Add("ConfigurationName", "Microsoft.Exchange");
            $hshSplat.Add("ConnectionUri", "$($Url)");
            $hshSplat.Add("Authentication", "Basic");
            $hshSplat.Add("Credential", $Credential);
            $hshSplat.Add("AllowRedirection", $true);
            $hshSplat.Add("SessionOption", $(New-PSSessionOption -Verbose:$false -Debug:$false -SkipRevocationCheck:$true -SkipCACheck:$true -SkipCNCheck:$true -ProxyAccessType "$($ProxyAccessType)"));
            $hshSplat.Add("Verbose", $false);
            $hshSplat.Add("Debug", $false);
            $hshSplat.Add("ErrorAction", "SilentlyContinue");
            $hshSplat.Add("WarningAction", "SilentlyContinue");
            
            ##### Loop The Session Creation
            Write-Debug "Loop The Session Creation";
            [System.Int32]$intI = 0;
            Do
            {
                ##### Establish The Remote PSSession Session
                Write-Verbose "Establish A Remote PSSession Session ""$($strSessionName)""";
                [System.Management.Automation.Runspaces.PSSession]$objPsSession = $null;
                [System.Management.Automation.Runspaces.PSSession]$objPsSession = New-PSSession @hshSplat;
                
                ##### Process The Loop Variable And TimeOut
                Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
            }
            While (("$($objPsSession.Name)" -ne "$($strSessionName)") -and ($intI -lt $Attempts))
        }
        
        ##### Verify Session Creation
        Write-Debug "Verify Session Creation";
        If (-not "$($objPsSession.Name)")
        {
            [System.String]$strEventMessage = "Failed To Establish PowerShell Session ""$($strSessionName)""";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Create ExchangeOnline Session
        #==================================================================================
        
        #==================================================================================
        #region Import The Remote PSSession Session
        #==================================================================================
        
        ##### Verify Whether The Session Is Established
        Write-Debug "Verify Whether The Session Is Established";
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        If (-not -not "$($objPsSession.Name)")
        {
            ##### Generate The Import-PSSession Splatting HashTable
            Write-Debug "Generate The Import-PSSession Splatting HashTable";
            [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
            $hshSplat.Add("Session", $objPsSession);
            $hshSplat.Add("AllowClobber", $true);
            $hshSplat.Add("DisableNameChecking", $true);
            #$hshSplat.Add("Verbose", $false); ##### Dont Uncomment Shows A Lot Of Verbose Messages Even With False Value
            $hshSplat.Add("Debug", $false);
            $hshSplat.Add("ErrorAction", "SilentlyContinue");
            $hshSplat.Add("WarningAction", "SilentlyContinue");
            
            ##### Verify Whether Prefix Is Specified
            Write-Debug "Verify Whether Prefix Is Specified";
            If (-not -not "$($Prefix)") { $hshSplat.Add("Prefix", "$($Prefix)"); }
            
            ##### Import The Remote PSSession Session
            Write-Verbose "Importing The Remote PSSession With Prefix ""$($Prefix)""";
            $VerbosePreference = "SilentlyContinue";
            [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
            [System.Management.Automation.PSModuleInfo]$objSesModule = Import-Module -ModuleInfo ( Import-PSSession @hshSplat ) `
                -Prefix "$($Prefix)" -Global:$true -DisableNameChecking:$true -Force:$true -PassThru:$true -Debug:$false;
            #####
        }
        
        ##### Verify The PSSession Import
        Write-Debug "Verify The PSSession Import";
        If (-not "$($objSesModule.Name)")
        {
            [System.String]$strEventMessage = "Failed To Import The Remote PSSession ""$($strSessionName)"".";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Import The Remote PSSession Session
        #==================================================================================
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objPsSession";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplat";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        return $objSesModule;
    }
}

##### Prepare-SnsHostForRemoteSessionsHelper ======================================
Function Prepare-SnsHostForRemoteSessionsHelper ()
{
<#
.SYNOPSIS
Modifies The Host Machine Settings To Allow Remote PSSession To Office365.
.DESCRIPTION
Modifies The Host Machine Settings To Allow Remote PSSession To Office365.
The CmdLet Performs The Following Actions In Sequence:
-- Check WinRM Service Existence
-- Check Whether WinRM Service Is Set As Automatic And Modify It If Needed.
-- Check Whether WinRM Service Is Running And Start It If Needed.
-- Check AllowBasic Registry Key Existence
-- Check Whether AllowBasic Registry Key Have Value 1 And Modify It If Needed.
This CmdLet Is Internal For The Module And Will Not Be Exported To The User.
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER WhatIf
Specifies To The CmdLet To Show All The Actions That Will Normally Do Without Doing Them.
Lack Of Errors During WhatIf Execution Is Not Indicator That There Will Be No Any During Real Execution.
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Boolean[]] Which Revert Information Whether All Required Settings Are Correct
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Boolean[]]$bolSuccess = Prepare-SnsHostForRemoteSessionsHelper "$($EventSource)";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $true, ConfirmImpact = "Low")]
Param (
    [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Prepare-SnsHostForRemoteSessionsHelper";
        #Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)`r`n";
        
        ##### Initialize The Variables
        [System.Array]$arrSrvc = @();
        [System.Array]$arrRgstr = @();
        
        #==================================================================================
        #region Set WinRM Service To Automatic And Start It
        #==================================================================================
        
        ##### Get WinRM Service
        Write-Debug "Get WinRM Service";
        [System.Array]$arrSrvc = @();
        [System.Array]$arrSrvc = Get-Service -Name "WinRM" -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue";
        
        ##### Verify WinRM Service Existence
        Write-Debug "Verify WinRM Service Existence";
        If (($arrSrvc.Count -eq 1) -and ("$($arrSrvc[0].Name)" -eq "WinRM"))
        {
            #==================================================================================
            #region Set WinRM Service To Automatic
            #==================================================================================
            
            ##### Verify Whether WinRM Service Is Automatic
            Write-Debug "Verify Whether WinRM Service Is Automatic";
            If ("$($arrSrvc[0].StartType)" -ne "Automatic")
            {
                ##### Check If The CmdLet Should Process
                If ($PSCmdlet.ShouldProcess("Set-Service WinRM To Automatic"))
                {
                    ##### Set WinRM Service To Automatic
                    Write-Debug "Set-Service WinRM To Automatic"; 
                    Set-Service -Name "WinRM" -StartupType "Automatic" -Confirm:$false -Verbose:$false -Debug:$false | Out-Null;
                    Start-Sleep -Seconds 5 -Verbose:$false -Debug:$false;
                    
                    ##### Get WinRM Service
                    Write-Debug "Get WinRM Service";
                    [System.Array]$arrSrvc = @();
                    [System.Array]$arrSrvc = Get-Service -Name "WinRM" -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue";
                    
                    ##### Verify Whether WinRM Is Properly Reconfigured
                    Write-Debug "Verify Whether WinRM Is Properly Reconfigured";
                    If ("$($arrSrvc[0].StartType)" -ne "Automatic")
                    {
                        [System.String]$strEventMessage = "Failed To Set WinRM To Automatic";
                        Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                        Return $false;
                    }
                }
            }
            
            #==================================================================================
            #endregion Set WinRM Service To Automatic
            #==================================================================================
            
            #==================================================================================
            #region Start WinRM Service
            #==================================================================================
            
            ##### Verify Whether WinRM Service Is Running
            Write-Debug "Verify Whether WinRM Service Is Running";
            If ("$($arrSrvc[0].Status)" -ne "Running")
            {
                ##### Check If The CmdLet Should Process
                If ($PSCmdlet.ShouldProcess("Start-Service WinRM"))
                {
                    ##### Start WinRM Service
                    Write-Debug "Start WinRM Service"; 
                    Start-Service -Name "WinRM" -Confirm:$false -Verbose:$false -Debug:$false | Out-Null;
                    Start-Sleep -Seconds 5 -Verbose:$false -Debug:$false;
                    
                    ##### Get WinRM Service
                    Write-Debug "Get WinRM Service";
                    [System.Array]$arrSrvc = @();
                    [System.Array]$arrSrvc = Get-Service -Name "WinRM" -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue";
                    
                    ##### Verify Whether WinRM Is Properly Started
                    Write-Debug "Verify Whether WinRM Is Properly Started";
                    If ("$($arrSrvc[0].Status)" -ne "Running")
                    {
                        [System.String]$strEventMessage = "Failed To Start WinRM Service";
                        Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                        Return $false;
                    }
                }
            }
            
            #==================================================================================
            #endregion Start WinRM Service
            #==================================================================================
        }
        
        #==================================================================================
        #endregion Set WinRM Service To Automatic And Start It
        #==================================================================================
        
        #==================================================================================
        #region Allow Basic Authnetication
        #==================================================================================
        
        ##### Get AllowBasic Registry Key Value
        Write-Debug "Get AllowBasic Registry Key Value";
        [System.Array]$arrRgstr = @();
        If (Test-Path -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client")
        {
            Try
            {
                ##### Even With SilentlyContinue The .NET Exceptions Can Prevent From Continue
                [System.Array]$arrRgstr = Set-SnsRegistry -RegistryPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client" -RegistryName "AllowBasic" -RegistryType "DWord" `
                    -RegistryValue "1" -PassThru:$true -WhatIf:$true -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue" -WarningAction "SilentlyContinue";
                #####
            }
            Catch {}
            
            ##### Verify Whether AllowBasic Registry Key Value Exists And The Value Is Correct
            Write-Debug "Verify Whether AllowBasic Registry Key Value Exists And The Value Is Correct";
            If (("$($arrRgstr[0].RegistryName)" -eq "AllowBasic") -and (-not $arrRgstr[0].ValueCorrect))
            {
                ##### Check If The CmdLet Should Process
                If ($PSCmdlet.ShouldProcess("Enable WinRM Basic Authentication"))
                {
                    ##### Enable WinRM Basic Authentication
                    Write-Debug "Enable WinRM Basic Authentication";
                    [System.Array]$arrRgstr = @();
                    [System.Array]$arrRgstr = Set-SnsRegistry -RegistryPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client" -RegistryName "AllowBasic" -RegistryType "DWord" `
                        -RegistryValue "1" -PassThru:$true -WhatIf:$false -Confirm:$false -Force:$true -Verbose:$false -Debug:$false;
                    #####
                    
                    ##### Verify WinRM Basic Authentication Enablement
                    Write-Debug "Verify WinRM Basic Authentication Enablement";
                    If (-not $arrRgstr[0].ValueCorrect)
                    {
                        [System.String]$strEventMessage = "Failed To Enable WinRM Basic Authentication";
                        Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                        Return $false;
                    }
                }
            }
        }
        
        #==================================================================================
        #endregion Allow Basic Authnetication
        #==================================================================================
        
        ##### Pass The Output Object To The Pipeline
        Write-Debug "Pass Output Object To The Pipeline";
        $PSCmdlet.WriteObject($true);
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrRgstr";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrSrvc";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### Remove-SnsPreviousSessionsHelper ============================================
Function Remove-SnsPreviousSessionsHelper ()
{
<#
.SYNOPSIS
Removes Any Established Remote PsSessions That Match The Specified Filter Along With Associated To Them Temporary
PowerShell Modules.
.DESCRIPTION
Removes Any Established Remote PsSessions That Match The Specified Filter Along With Associated To Them Temporary
PowerShell Modules.
This Is Internal Helper CmdLet Not Available To The User.
.PARAMETER Filter
Specifies The Filter Which Will Be Applied Against The Session Name.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER ScrBlock
Specifies The Verification ScriptBlock Used To Verify Whether The Remote PSSession Is Established.
Parameter Set: N/A
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
No Output
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
Remove-SnsPreviousSessionsHelper """$($_.Name)"" -like ""ExchangeOnlineInternalSession*""";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.String]$Filter,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.Management.Automation.ScriptBlock]$ScrBlock
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Initialize The Variables
        [System.Array]$arrTmp = @();
        [System.String[]]$arrModules = @();
        [System.Int32]$intI = 0;
        
        ##### Enumerate Any Previous Sessions
        Write-Debug "Enumerate Any Previous Sessions";
        [System.Array]$arrTmp = @();
        [System.Array]$arrTmp = Get-PSSession -Verbose:$false -Debug:$false | Where-Object -Verbose:$false -Debug:$false -Property "Name" -like "$($Filter)*" | `
            Select-Object -Verbose:$false -Debug:$false -Property @("Name", "CurrentModuleName");
        #####
        
        ##### Verify Any Previous Sessions Existence
        Write-Debug "Verify Any Previous Sessions Existence";
        If ($arrTmp.Count -gt 0)
        {
            ##### Loop All Previous Sessions
            [System.Int32]$intI = 0;
            For ([System.Int32]$intI = 0; $intI -lt $arrTmp.Count; $intI++)
            {
                ##### Disconnect The Previous Sessions
                Write-Verbose "Removing Previous Session: ""$($arrTmp[$intI].Name)"""; 
                Remove-PSSession -Name "$($arrTmp[$intI].Name)" -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false;
                
                ##### Verify Whether The Temporary Module Is Enumerated
                Write-Debug "Verify Whether The Temporary Module Is Enumerated";
                If (-not "$($arrTmp[$intI].CurrentModuleName)")
                {
                    [System.String[]]$arrModules = Get-Module -Verbose:$false -Debug:$false | `
                        Where-Object -Verbose:$false -Debug:$false {"$($_.Name)".StartsWith("tmp_")} | `
                        Where-Object -Verbose:$false -Debug:$false {$_.ExportedCommands.Keys -icontains "$($scrBlock)"} | `
                        Select-Object  -Verbose:$false -Debug:$false -ExpandProperty "Name";
                    #####
                } Else { [System.String[]]$arrModules = @("$($arrTmp[$intI].CurrentModuleName)"); }
                
                ##### Verify Whether There Are Temporary Modules For Removal
                Write-Debug "Verify Whether There Are Temporary Modules For Removal";
                If ($arrModules.Count -gt 0)
                {
                    ##### Loop All Previous Sessions
                    [System.Int32]$intIn = 0;
                    For ([System.Int32]$intIn = 0; $intIn -lt $arrModules.Count; $intIn++)
                    {
                        ##### Verify Whether The Temporary Module Is Enumerated
                        Write-Debug "Verify Whether The Temporary Module Is Enumerated";
                        If (-not -not "$($arrModules[$intIn])")
                        {
                            ##### Remove The Previous Temporary Module
                            Write-Verbose "Removing Previous Temporary Module: ""$($arrModules[$intIn])""";
                            Remove-Module -Name "$($arrModules[$intIn])" -Force:$true -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false;
                        }
                    }
                }
            }
        }
        
        ##### Prepare The Host Machine For Remote PSSession
        Write-Debug "Prepare The Host Machine For Remote PSSession";
        If (-not (Prepare-SnsHostForRemoteSessionsHelper "$($EventSource)" -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false)) { Return; }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrTmp";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### Test-SnsCredentialHelper ====================================================
Function Test-SnsCredentialHelper ()
{
<#
.SYNOPSIS
Verifies The Specified Credentials Against The Current Active Directory Domain.
.DESCRIPTION
Verifies The Specified Credentials Against The Current Active Directory Domain.
This Is A Helper Function And Won't Be Exported / Available To The User.
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Boolean] Which Indicates Whether The Specified Credentials Are Valid.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Boolean]$bolCheckCred = Test-SnsCredentialHelper -Credential $Credential -Verbose:$bolVerbose;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Verify Whether The CmdLet Is Executed In Active Directory Domain
        ##### On Premises Exchange Can Live Only In Active Directory Environment
        Write-Debug "Verify Whether The CmdLet Is Executed In Active Directory Domain";
        [System.String]$strDomainName = "";
        [System.String]$strDomainDn = "$(([ADSI]"""").distinguishedName)";
        If (-not -not "$($strDomainDn)")
        {
            [System.String]$strDomainName = "$((New-Object -TypeName ""System.DirectoryServices.DirectoryEntry"" -Verbose:$false -Debug:$false -ArgumentList `
                @( ""LDAP://$($strDomainDn)"", ""$($objCredential.UserName)"", ""$($objCredential.GetNetworkCredential().Password)"" )).name)"
;
            #####
        }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strDomainDn";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "Credential";
        
        Return (-not -not "$($strDomainName)");
    }
}

#==================================================================================
#endregion Helper Comands
#==================================================================================

#==================================================================================
#region Commands
#==================================================================================

##### Assert-SnsDirectAssignedLicense =============================================
Function Assert-SnsDirectAssignedLicense ()
{
<#
.SYNOPSIS
Evaluates Whether Specified Licenses Is Assigned To The Specified User Directly Using PowerShell Or Admin Portal.
.DESCRIPTION
Evaluates Whether Specified Licenses Is Assigned To The Specified User Directly Using PowerShell Or Admin Portal.
This CmdLet Search Among The Licenses Assigned To The User About The Specified License. Then Reads The
GroupsAssigningLicense Property Of The Specified License. This License Property Normally Contains A Collection Of
Object ID's Of The Objects That Have Assigned That License.
In Case The License Is Assigned Directly Via PowerShell Or The Admin Portal, There Will Be The Users ObjectID (Not
Administrators One) Or The Collection Will Be Empty. The Value Of The Property Can Neither Be Used For
Identification Who Did Assigned The License, Nor For Troubleshooting, Because The Value There Is The Users Own
ObjectID. The Collection Will Be Empty Whenever The Group Based License Feature Were Never Used In The Tenant.
In Case The User Inherits Specified License From A Group Or Groups, The Collection Will Contain The ObjectID Of
The Groups Which Assign The Specified License To The User. In That Way, The ObjectID's From The Collection Can Be
Used For Troubleshooting.
Note: A License Might Be Assigned Directly In Addition To Being Inherited. In Case A License Is Directly Assigned
Will Be Wrong Assumption That It Is Not Inherited From A Group.
Note: The CmdLet Does Not Perform Any Queries To Msol V1 Service As Long As All The Required Information Is
Already Present In The Required Input. It Just Extracts The Information From There.
.PARAMETER MsolUser
Specifies MsolUser Object Which Have To Be Evaluated.
Parameter Alias: N/A
Parameter Validation: Yes Using Object TypeName Validation
.PARAMETER SkuId
Specifies The ID Of The License Which Have To Be Evaluated.
Parameter Alias: N/A
Parameter Validation: Yes, Using RegEx Validation
.INPUTS
The CmdLet Does Not Accept Pipeline Input.
.OUTPUTS
Pipeline Output [System.Boolean] Which Indicates Whether The Specified User Have The Specified License Directly
Assigned.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Boolean]$bolGroupAssigned = Assert-SnsDirectAssignedLicense -MsolUser $objUser `
-SkuId "contoso:ENTERPRISEPACK";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("User")]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({("$(($_ | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -like "*Microsoft.Online.Administration.User")})]
    [System.Object]$MsolUser,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({("$($_)" -match "^[a-z]+:[a-zA-Z0-9_]+$")})]
    [System.String]$SkuId
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Assert-SnsDirectAssignedLicense";
        #Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)`r`n";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        If ("$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"" -Verbose:$false -Debug:$false).Hash)" -ne "$($global:SnsModuleCfg.ModuleHash)")
        { Write-Warning "There Is New Version Of SnsPsModule Module Released. Please Restart The PowerShell Session." -WarningAction "Continue"; };
        
        ##### Initialize The Variables
        [System.Int32]$intI = 0;
        [System.Int32]$intIn = 0;
        [System.Boolean]$bolDirect = $false;
        
        ##### Continue If The User Have Licenses Assigned
        Write-Debug "Continue If The User Have Licenses Assigned";
        If ($MsolUser.Licenses.Count -gt 0)
        {
            ##### Process All Assigned To The User Licenses
            Write-Debug "Process All Assigned To The User Licenses";
            [System.Int32]$intI = 0;
            For  ([System.Int32]$intI = 0; $intI -lt $MsolUser.Licenses.Count; $intI++)
            {
                ##### We Look For The Specified License SKU In All Licenses Assigned To The User
                If ("$($MsolUser.Licenses[$intI].AccountSkuId)" -eq "$($SkuId)")
                {
                    ##### GroupsAssigningLicense Property Contains A Collection Of IDs Of Objects Assigning The License
                    ##### This Could Be A Group Object Or A User Object (Contrary To What The Name Suggests)
                    ##### If The Collection Contains At Least One ID Not Matching The User ID This Means That The License Is Inherited From A Group.
                    ##### Note: The License May Also Be Assigned Directly In Addition To Being Inherited
                    ##### In Case In The Tenant Were Never Used Group Based Licensing The GroupsAssigningLicense Will Be Empty
                    ##### Verify The Count Of The License Assigning Sources
                    Write-Debug "Verify The Count Of The License Assigning Sources";
                    If ($MsolUser.Licenses[$intI].GroupsAssigningLicense.Count -gt 0)
                    {
                        ##### Process Each License Assigning Source As It Can Be A Collection
                        Write-Debug "Process Each License Assigning Source";
                        [System.Int32]$intIn = 0;
                        For ([System.Int32]$intIn = 0; $intIn -lt $MsolUser.Licenses[$intI].GroupsAssigningLicense.Count; $intIn++)
                        {
                            ##### Check If The Current Assignment Source Is The User Himself
                            If ("$($MsolUser.Licenses[$intI].GroupsAssigningLicense[$intIn].Guid)" -eq "$($MsolUser.ObjectId)")
                            {
                                ##### This License Is Directly Assigned
                                Write-Verbose "The License $($SkuId) Is Directly Assigned To The User";
                                $bolDirect = $true;
                            }
                        }
                    }
                    Else
                    {
                        ##### There Are No ObjectID In The Object Property
                        ##### Which Means The Tenant Has Never GBL
                        ##### Which MEans That The License Can Be Assigned Only Directly
                        Write-Verbose "The License $($SkuId) Is Directly Assigned To The User";
                        [System.Boolean]$bolDirect = $true;
                    }
                }
            }
        }
        
        ##### Pass The Output Object To The Pipeline
        Write-Debug "Pass Output Object To The Pipeline";
        $PSCmdlet.WriteObject($bolDirect);
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolDirect";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intIn";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
    }
}

##### Assert-SnsGroupBasedLicense =================================================
Function Assert-SnsGroupBasedLicense ()
{
<#
.SYNOPSIS
Evaluates Whether Specified Licenses Is Assigned To The Specified User Using AzureAD Group Based Licensing.
.DESCRIPTION
Evaluates Whether Specified Licenses Is Assigned To The Specified User Using AzureAD Group Based Licensing.
This CmdLet Search Among The Licenses Assigned To The User About The Specified License. Then Reads The
GroupsAssigningLicense Property Of The Specified License. This License Property Normally Contains A Collection Of
Object ID's Of The Objects That Have Assigned That License.
In Case The License Is Assigned Directly Via PowerShell Or The Admin Portal, There Will Be The Users ObjectID (Not
Administrators One) Or The Collection Will Be Empty. The Value Of The Property Can Neither Be Used For
Identification Who Did Assigned The License, Nor For Troubleshooting, Because The Value There Is The Users Own
ObjectID. The Collection Will Be Empty Whenever The Group Based License Feature Were Never Used In The Tenant.
In Case The User Inherits Specified License From A Group Or Groups, The Collection Will Contain The ObjectID Of
The Groups Which Assign The Specified License To The User. In That Way, The ObjectID's From The Collection Can Be
Used For Troubleshooting. For That Purpose, The CmdLet Have Switch Parameter PassThru, Which Is Used To Revert The
ObjectID Of The Groups Assigning The Specified License To The Specified User.
Note: A License Might Be Assigned Directly In Addition To Being Inherited. In Case A License Is Inherited Will Be
Wrong Assumption That It Is Not Directly Assigned.
Note: The CmdLet Does Not Perform Any Queries To Msol V1 Service As Long As All The Required Information Is
Already Present In The Required Input. It Just Extracts The Information From There.
.PARAMETER MsolUser
Specifies MsolUser Object Which Have To Be Evaluated.
Parameter Alias: N/A
Parameter Validation: Yes Using Object TypeName Validation
.PARAMETER SkuId
Specifies The ID Of The License Which Have To Be Verified.
Parameter Alias: N/A
Parameter Validation: Yes, Using RegEx Validation
.PARAMETER PassThru
Specifies That The CmdLet Have To Revert The ObjectID's Of The Groups That The Specified User Inherits The
Specified License From.
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
The CmdLet Does Not Accept Pipeline Input.
.OUTPUTS
Pipeline Output [System.Boolean] Which Indicates Whether The Specified User Inherits The Specified License From
Groups.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Boolean]$bolGroupAssigned = Assert-SnsGroupBasedLicense -MsolUser $objUser `
-SkuId "contoso:ENTERPRISEPACK";
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false)]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("User")]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({("$(($_ | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -like "*Microsoft.Online.Administration.User")})]
    [System.Object]$MsolUser,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({("$($_)" -match "^[a-z]+:[a-zA-Z0-9_]+$")})]
    [System.String]$SkuId,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$PassThru = $false
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Assert-SnsGroupBasedLicense";
        #Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)`r`n";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        If ("$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"" -Verbose:$false -Debug:$false).Hash)" -ne "$($global:SnsModuleCfg.ModuleHash)")
        { Write-Warning "There Is New Version Of SnsPsModule Module Released. Please Restart The PowerShell Session." -WarningAction "Continue"; };
        
        ##### Initialize The Variables
        [System.Int32]$intI = 0;
        [System.Int32]$intIn = 0;
        [System.Boolean]$bolGbl = $false;
        
        ##### Continue If The User Have Licenses Assigned
        Write-Debug "Continue If The User Have Licenses Assigned";
        If ($MsolUser.Licenses.Count -gt 0)
        {
            ##### Process All Assigned To The User Licenses
            Write-Debug "Process All Assigned To The User Licenses";
            [System.Int32]$intI = 0;
            For  ([System.Int32]$intI = 0; $intI -lt $MsolUser.Licenses.Count; $intI++)
            {
                ##### We Look For The Specified License SKU In All Licenses Assigned To The User
                If ("$($MsolUser.Licenses[$intI].AccountSkuId)" -like "$($SkuId)")
                {
                    ##### GroupsAssigningLicense Property Contains A Collection Of IDs Of Objects Assigning The License
                    ##### This Could Be A Group Object Or A User Object (Contrary To What The Name Suggests)
                    ##### If The Collection Contains At Least One ID Not Matching The User ID This Means That The License Is Inherited From A Group.
                    ##### Note: The License May Also Be Assigned Directly In Addition To Being Inherited
                    ##### In Case In The Tenant Were Never Used Group Based Licensing The GroupsAssigningLicense Will Be Empty
                    ##### Verify The Count Of The License Assigning Sources
                    Write-Debug "Verify The Count Of The License Assigning Sources";
                    If ($MsolUser.Licenses[$intI].GroupsAssigningLicense.Count -gt 0)
                    {
                        ##### Process Each License Assigning Source As It Can Be A Collection
                        Write-Debug "Process Each License Assigning Source";
                        [System.Int32]$intIn = 0;
                        For ([System.Int32]$intIn = 0; $intIn -lt $MsolUser.Licenses[$intI].GroupsAssigningLicense.Count; $intIn++)
                        {
                            ##### Check If The Current Assignment Source Belongs To Object Different Than The User Himself
                            If ("$($MsolUser.Licenses[$intI].GroupsAssigningLicense[$intIn].Guid)" -ne "$($MsolUser.ObjectId)")
                            {
                                ##### This License Is Group Inherited
                                Write-Verbose "The License $($SkuId) Is Assigned To The User Via Group $($MsolUser.Licenses[$intI].GroupsAssigningLicense[$intIn].Guid)";
                                $bolGbl = $true;
                                
                                ##### Verify Whether Return Object Has Been Requested
                                If ($PassThru.IsPresent)
                                {
                                    ##### Pass The Output Object To The Pipeline
                                    Write-Debug "Pass Output Object To The Pipeline";
                                    $PSCmdlet.WriteObject("$($MsolUser.Licenses[$intI].GroupsAssigningLicense[$intIn].Guid)");
                                }
                            }
                        }
                    }
                }
            }
        }
        
        ##### Verify Whether Return Object Has Been Requested
        If (-not $PassThru.IsPresent)
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            $PSCmdlet.WriteObject($bolGbl);
        }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolGbl";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intIn";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
    }
}

##### Connect-SnsAzureAd ==========================================================
Function Connect-SnsAzureAd ()
{
<#
.SYNOPSIS
Establishes A Remote PowerShell Session To Office 365 AzureAD V2 Service.
.DESCRIPTION
Establishes A Remote PowerShell Session To Office 365 AzureAD V2 Service.
The CmdLet Have Five Parameter Sets Related With The Authentication Method:
-- FolderPath Here Must Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted
Password File Resides.
-- FilePath Here Must Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To
Enumerate The UserName From The FileName.
-- Credential Here Must Be Provided System.Management.Automation.PSCredential Object.
-- CertificateThumbprint Uses Microsoft Authentication Libraries (MSAL): https://bit.ly/3x4bhEH. For That Purpose
An Azure App Registration With Service Principal Have To Be Created And The Required Roles To Be Assigned To The
Service Principal As Described Here: https://bit.ly/3FuQRYj
-- Interactive This Is The Only Parameter Set Which Is Capable To Establish Remote PowerShell Session To AzureAD
V2 Service With Multifactor Authentication. However It Cannot Be Used Whenever The Script Or The Function Is
Executed In As Service Mode.
In This Parameter Set The CmdLet Opens A Window Where The User Can Specify His Credentials And Multi Factor
Authentication Code Received On A SMS Or Inside A Phone App Or Phone Call And Etc.
Obviously When The CmdLet Is Executed As Service There Is No Real Person To Specify The Credentials And The MFA
Code. Depending Of The Configuration There Might Not Be PowerShell Host Console Window Either.
NOTE: The CmdLet Requires AzureAD V2 Module To Be Installed In Advance. Please Refer To https://bit.ly/30VWxaV
NOTE: There Must Be A Direct Connection / Firewall Openings To Office 365. Proxy Usage Is Not Allowed.
.PARAMETER UserName
Specifies The UserName In UPN Format.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Via RegEx Matching
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Resides.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Via Directory Existence Validation
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: Yes Using File Existence And File Extension Validation
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object.
Parameter Set: Credential
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER CertificateThumbprint
Specifies The Thumbprint Of A Certificate Used To Authenticate As Azure ServicePrincipal.
Parameter Set: CertificateThumbprint
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER ApplicationId
Specifies The ApplicationId Of Azure ServicePrincipal.
Parameter Set: CertificateThumbprint
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Matching Validation
.PARAMETER Tenant
Specifies The TenantId.
Parameter Set: CertificateThumbprint
Parameter Alias: TenantId
Parameter Validation: Yes Using RegEx Matching Validation
.PARAMETER Interactive
Specifies That The User Have To Be Asked Interactively For Credentials.
Parameter Set: Interactive
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: All
Parameter Alias: ScriptName
Parameter Validation: N/A
.PARAMETER PassThru
Specifies That The CmdLet Have To Revert A Verification Collection.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Array] Which Contains A List With The Available AzureAD License Objects.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Array]$arrAzureLicenses = Connect-SnsAzureAd -UserName 'john.smith@contoso.com' -FolderPath 'C:\';
.EXAMPLE
[System.Array]$arrAzureLicenses = Connect-SnsAzureAd -FilePath 'C:\john.smith@contoso.com.ini';
.EXAMPLE
[System.Array]$arrAzureLicenses = Connect-SnsAzureAd -Credential $objCredential;
.EXAMPLE
[System.Array]$arrAzureLicenses = Connect-SnsAzureAd -CertificateThumbprint "THISISADEMOTHUMBPRINT" `
-Tenant "00000000-0000-0000-0000-000000000000" -ApplicationId "00000000-0000-0000-0000-000000000000";
.EXAMPLE
[System.Array]$arrAzureLicenses = Connect-SnsAzureAd -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "Interactive")]
Param (
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("CredentialFile")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::CertThumbprintPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$CertificateThumbprint,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$ApplicationId,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("TenantId")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$Tenant,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 3,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ScriptName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({([SnsPsModule.SnsEventLog]::VerifySnsEventSource("$($_)"))})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$PassThru
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Connect-SnsAzureAd";
        Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)`r`n";
        Write-Verbose "ErrorAction: $($ErrorActionPreference)";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        If ("$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"" -Verbose:$false -Debug:$false).Hash)" -ne "$($global:SnsModuleCfg.ModuleHash)")
        { Write-Warning "There Is New Version Of SnsPsModule Module Released. Please Restart The PowerShell Session." -WarningAction "Continue"; };
        
        ##### Initialize The Variables
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        [System.Int32]$intI = 0;
        [System.Array]$arrClLics = @();
        
        #==================================================================================
        #region Enumerate The Credentials Object
        #==================================================================================
        
        ##### Load AzureADPreview PowerShell Module
        Write-Debug "Load AzureADPreview PowerShell Module";
        If (Import-SnsModuleHelper -Module "AzureADPreview" -EventSource "$($EventSource)") { Return; }
        
        ###### Generate The Credential Object In FolderPath Parameter Set
        Write-Debug "Verify The Parameter Set Name";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "CertificateThumbprint" { Break; }
            
            "FilePath"
            {
                Write-Verbose "Import The Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -FilePath "$($FilePath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Break;
            }
            
            "FolderPath"
            {
                Write-Verbose "Import The Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -UserName "$($UserName)" -FolderPath "$($FolderPath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Break;
            }
            
            "Credential"
            {
                Write-Verbose "Assign The Provided Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = $Credential;
                Break;
            }
        }
        
        ##### Verify The Credential Object
        Write-Debug "Verify The Credential Object";
        If (("$($PSCmdlet.ParameterSetName)" -ne "CertificateThumbprint") -and (-not [System.Environment]::UserInteractive) -and (-not "$($objCredential.UserName)"))
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.String]$strEventMessage = "Failed To Enumerate The AzureAd V2 Service Credential.";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Enumerate The Credentials Object
        #==================================================================================
        
        #==================================================================================
        #region Create AzureAD V2 Service Connection
        #==================================================================================
        
        ##### Verify Any Previous Sessions Existence
        Write-Debug "Verify Any Previous Sessions Existence";
        If (-not -not (Get-Variable -Verbose:$false -Debug:$false | Where-Object -Verbose:$false -Debug:$false {"$($_.Name)" -like "ArrAzureAdLicenses"}))
        {
            Write-Verbose "Removing Previous Sessions"; 
            Disconnect-AzureAD -Verbose:$false -Debug:$false;
        }
        
        ##### Generate The Connect-AzureAD Splatting HashTable
        Write-Debug "Generate The Connect-AzureAD Splatting HashTable";
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        $hshSplat.Add("Verbose", $false);
        $hshSplat.Add("Debug", $false);
        
        ##### Verify The ParameterSetName
        Write-Debug "Verify The ParameterSetName";
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "CertificateThumbprint"
            {
                $hshSplat.Add("TenantId", "$($Tenant)");
                $hshSplat.Add("ApplicationId", "$($ApplicationId)");
                $hshSplat.Add("CertificateThumbprint", "$($CertificateThumbprint)");
                Break;
            }
            
            "Interactive"
            {
                #==================================================================================
                #region Interactively Authenticate Against AzureAD
                #==================================================================================
                
                ##### Verify Whether It Is The First Interactive Logon
                Write-Debug "Verify Whether It Is The First Interactive Logon";
                If ( `
                    (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object -Verbose:$false -Debug:$false {"$($_.Name)" -like "AzureAdAccount"})) `
                    -or `
                    (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object -Verbose:$false -Debug:$false {"$($_.Name)" -like "AzureAdTenantId"})) `
                )
                {
                    ##### Load AzureRM.Profile PowerShell Module
                    Write-Debug "Load AzureRM.Profile PowerShell Module";
                    If (Import-SnsModuleHelper -Module "AzureRM.Profile" -EventSource "$($EventSource)") { Return; }
                    
                    ##### Loop The Interactive Login Process
                    Write-Debug "Loop The Interactive Login Process";
                    [System.Int32]$intI = 0;
                    [System.Object]$objRmAccount = $null;
                    [System.String]$strAccountId = "";
                    [System.String]$strTenantId = "";
                    Do
                    {
                        ##### LogOn To RmAccount
                        Write-Verbose "LogOn To RmAccount";
                        [System.Object]$objRmAccount = $null;
                        [System.Object]$objRmAccount = Login-AzureRmAccount -Verbose:$false -Debug:$false;
                        
                        ##### Generate The Account ID
                        Write-Debug "Generate The Account ID";
                        [System.String]$strAccountId = "";
                        [System.String]$strAccountId = "$($objRmAccount.Context.Account.Id)";
                        
                        ##### Generate The Tenant ID
                        Write-Debug "Generate The Tenant ID";
                        [System.String]$strTenantId = "";
                        [System.String]$strTenantId = "$(($objRmAccount.Context.Tenant | Where-Object {""$($strAccountId)"" -like ""*$($_.Directory)""} -Verbose:$false -Debug:$false)[0].Id)";
                        
                        ##### Increment The Counter
                        [System.Int32]$intI = $intI + 1;
                    }
                    While (((-not "$($strAccountId)") -or (-not "$($strTenantId)")) -and ($intI -lt $Attempts))
                    
                    ##### Verify The Token Generation
                    Write-Debug "Verify The Token Generation";
                    If ((-not -not "$($strAccountId)") -and (-not -not "$($strTenantId)"))
                    {
                        ##### Generate The Token Global Variables
                        New-Variable -Scope "Global" -Option "Constant" -Name "AzureAdAccount" -Value "$($strAccountId)";
                        New-Variable -Scope "Global" -Option "Constant" -Name "AzureAdTenantId" -Value "$($strTenantId)";
                    }
                    
                    ##### Reset The Variables
                    Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strTenantId";
                    Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strAccountId";
                    Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objRmAccount";
                }
                
                ##### Verify The Token Generation
                Write-Debug "Verify The Token Generation";
                If ((-not -not "$($global:AzureAdAccount)") -and (-not -not "$($global:AzureAdTenantId)"))
                {
                    ##### Add The Token Parameters To The Splatting HashTable
                    $hshSplat.Add("TenantId", "$($global:AzureAdTenantId)");
                    $hshSplat.Add('AccountId', "$($global:AzureAdAccount)");
                }
                Else
                {
                    [System.String]$strEventMessage = "Failed To Interactively Authenticate Against AzureAD V2 Service.";
                    Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                    Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                    Return;
                }
                
                #==================================================================================
                #endregion Interactively Authenticate Against AzureAD
                #==================================================================================
                
                Break;
            }
            
            default
            {
                $hshSplat.Add("Credential", $objCredential);
            }
        }
        
        ##### Loop The Session Creation
        Write-Debug "Loop The Session Creation";
        [System.Int32]$intI = 0;
        [System.Array]$arrClLics = @();
        Do
        {
            ##### Display The Action On The Console
            Write-Verbose "Connecting To AzureAD V2 Service.";
            Connect-AzureAD @hshSplat | Out-Null;
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
            
            ##### Verify The PowerShell Session To The AzureAD
            [System.Array]$arrClLics = @();
            [System.Array]$arrClLics = Get-AzureADSubscribedSku -Verbose:$false -Debug:$false | `
                Select-Object -Property @("SkuPartNumber", "SkuId", "ServicePlans", "ConsumedUnits") -ExpandProperty "PrepaidUnits" -Verbose:$false -Debug:$false;
            #####
        }
        While (($arrClLics.Count -lt 1) -and ($intI -lt $Attempts))
        
        ##### Verify The AzureAD Session Creation
        Write-Debug "Verify The AzureAD Session Creation";
        If ($arrClLics.Count -lt 1)
        {
            [System.String]$strEventMessage = "Failed To Establish A Connection To AzureAD V2 Service";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        Else
        {
            ##### Verify Whether The Global AzureAd Licenses Variable Exists
            Write-Debug "Verify Whether The Global AzureAd Licenses Variable Exists";
            If (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object -Verbose:$false -Debug:$false {"$($_.Name)" -like "ArrAzureAdLicenses"}))
            {
                ##### Create The Global AzureAd Licenses Variable
                Write-Debug "Create The Global AzureAd Licenses Variable";
                New-Variable -Scope "Global" -Option "Constant" -Name "ArrAzureAdLicenses" -Value ($arrClLics);
            }
        }
        
        #==================================================================================
        #endregion Create AzureAD V2 Service Connection
        #==================================================================================
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplat";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCredential";
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
        
        ##### Continue If Output Is Requested
        Write-Debug "Continue If Output Is Requested";
        If (($PassThru.IsPresent) -and ($arrClLics.Count -gt 0))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $arrClLics;
        }
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### Connect-SnsExchangeEws ======================================================
Function Connect-SnsExchangeEws ()
{
<#
.SYNOPSIS
This CmdLet Establish PowerShell Session To ExchangeOnline EWS Service Either On Premises Or Exchange Online.
.DESCRIPTION
This CmdLet Establish PowerShell Session To ExchangeOnline EWS Service Either On Premises Or Exchange Online.
The CmdLet Uses Basic Authentication, With Other Words By The End Of The Year It Will Stop Working With Exchange
Online. Please Consider Writing Of An Application With OAuth Authentication Against The Exchange Online EWS. The
CmdLet Will Continue To Work With Exchange Server.
In Case The Session Creation Fail The CmdLet Can Log An Event In The Windows Event Viewer Application Log And Kill
The Script Process. This Functionality Is Enabled Automatically When EventSource Parameter Is Provided. Simple
Throwing Terminating Error Will Keep The PowerShell Process. Which Will Prevent The Next Script Instances From
Execution. And Any Possible Script Monitoring Will Be Cheated That The Script Is Still Running.
The CmdLet Have Four Parameter Sets Related With The Way It Authenticates Against EWS:
-- FolderPath - Must Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted Password
File Resides.
-- FilePath - Must Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To Enumerate
The UserName From The FileName.
-- Credential - Must Be Provided System.Management.Automation.PSCredential Object.
-- Interactive It Cannot Be Used Whenever The Script Or The CmdLet Is Executed In As Service Mode.
In This Parameter Set The CmdLet Opens A Window Where The User Can Specify His Credentials.
Obviously When The CmdLet Is Executed As Service There Is No Real Person To Specify The Credentials.
.PARAMETER Mailbox
Specifies The PrimarySmtpAddress Of A Mailbox Used By The CmdLet To Enumerate The Connection Destination Via
Exchange Autodiscover.
If Omitted The CmdLet Will Try To Enumerate The Mailbox Of The Account Used For The Connection Authentication.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER UserName
Specifies The UserName Of The Account Used To Authenticate Against EWS.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Resides.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using Folder Existence Validation
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: Yes Using File Existence And File Extension Validation
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object.
Parameter Set: Credential
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Interactive
Specifies That The User Have To Be Asked Interactively For Credentials.
Parameter Set: Interactive
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: All
Parameter Alias: ScriptName
Parameter Validation: N/A
.PARAMETER PassThru
Specifies That The CmdLet Have To Revert A Verification Collection.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Object] Which Contains The Exchange Service Object.
The Output Is Provided Only If Requested Because The CmdLet Normally Creates A Global Variable With That Object.
The Global Variable Is: $global:ObjEwsService
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Object]$objExchSrvc = Connect-SnsExchangeEws -UserName "john.smith@contoso.com" -FolderPath "C:\";
.EXAMPLE
[System.Object]$objExchSrvc = Connect-SnsExchangeEws -FilePath "C:\john.smith@contoso.com.ini";
.EXAMPLE
[System.Object]$objExchSrvc = Connect-SnsExchangeEws -Credential $objCredential;
.EXAMPLE
[System.Object]$objExchSrvc = Connect-SnsExchangeEws -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "Interactive")]
Param (
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$Mailbox = "",
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({(("$($_)".Contains("\")) -or ("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)"))})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("CredentialFile")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 3,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ScriptName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({([SnsPsModule.SnsEventLog]::VerifySnsEventSource("$($_)"))})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$PassThru
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Connect-SnsExchangeEws";
        Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)`r`n";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        If ("$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"" -Verbose:$false -Debug:$false).Hash)" -ne "$($global:SnsModuleCfg.ModuleHash)")
        { Write-Warning "There Is New Version Of SnsPsModule Module Released. Please Restart The PowerShell Session." -WarningAction "Continue"; };
        
        Write-Warning "If You Are Using This CmdLet To Connect To Exchange Online EWS Please Consider Building Your Own App Using OAuth Authentication.";
        Write-Warning "This CmdLet Will Stop Working With Exchange Online Becuase It Uses Basic Authentication.";
        
        #==================================================================================
        #region Load Exchange Web Services Library
        #==================================================================================
        
        ##### Search About EWS Module
        Write-Debug "Search About EWS Module";
        [System.IO.FileInfo[]]$arrEwsModules = @();
        [System.IO.FileInfo[]]$arrEwsModules = Get-ChildItem -Path "C:\Program Files\Microsoft\Exchange\Web Services" -Filter "Microsoft.Exchange.WebServices.dll" `
            -Recurse:$true -Verbose:$false -Debug:$false | Sort-Object -Property "LastWriteTimeUtc" -Verbose:$false -Debug:$false;
        #####
        
        ##### Verify The Search Output
        Write-Debug "Verify The Search Output";
        If ($arrEwsModules.Count -le 0)
        {
            [System.String]$strEventMessage = "Exchange Web Services Library Is Not Installed`r`nPlease Refer To https://bit.ly/2vEg18e";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        ##### Load Exchange Web Services Library
        Write-Debug "Load Exchange Web Services Library";
        If ($arrEwsModules.Count -gt 0)
        {
            Write-Host "Load Exchange Web Services Library" -ForegroundColor "Green";
            [System.String]$strVerbosePreference = "$($VerbosePreference)"; $VerbosePreference = "SilentlyContinue";
            Import-Module -Name "$($arrEwsModules[0].FullName)" -Global:$true -Force:$true -Verbose:$false -Debug:$false;
            $VerbosePreference = "$($strVerbosePreference)";
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strVerbosePreference";
        }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrEwsModules";
        
        #==================================================================================
        #endregion Load Exchange Web Services Library
        #==================================================================================
        
        ##### Initialize The Variables
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Int32]$intI = 0;
        [System.Net.NetworkCredential]$objNwCred = $null;
        [Microsoft.Exchange.WebServices.Data.WebCredentials]$objEwsCred = $null;
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$objExchSrvc = $null;
        
        #==================================================================================
        #region Initialize The Credentials Object
        #==================================================================================
        
        ###### Verify The ParameterSetName
        Write-Debug "Verify The ParameterSetName";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "FilePath"
            {
                Write-Verbose "Import The Credential From File";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -FilePath "$($FilePath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Break;
            }
            
            "FolderPath"
            {
                Write-Verbose "Import The Credential From File";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -UserName "$($UserName)" -FolderPath "$($FolderPath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Break;
            }
            
            "Credential"
            {
                Write-Verbose "Assign The Provided Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = $Credential;
                Break;
            }
        }
        
        ##### Verify If It Is Interactive Session And There Are No Credentials
        Write-Debug "Verify If It Is Interactive Session And There Are No Credentials";
        If ((-not "$($objCredential.UserName)") -and [System.Environment]::UserInteractive)
        {
            ##### Loop Interactive Credentials Dialog With The User
            Write-Debug "Loop Interactive Credentials Dialog With The User";
            [System.Int32]$intI = 0;
            Do
            {
                ##### Ask The User About Credentials
                Write-Verbose "Ask The User About Credentials";
                [System.Management.Automation.PSCredential]$objCredential = $null;
                [System.Management.Automation.PSCredential]$objCredential = Get-Credential -Verbose:$false -Debug:$false;
                
                ##### Check The Imported Credentials
                Write-Debug "Check The Imported Credentials";
                If (-not "$($objCredential.UserName)")
                {
                    ##### Generate The Error Message
                    Write-Error "Provided Invalid Credentials" -ErrorAction "Continue";
                    [System.Management.Automation.PSCredential]$objCredential = $null;
                }
                
                ##### Increment The Counter
                [System.Int32]$intI = $intI + 1;
            }
            While ((-not "$($objCredential.UserName)") -and ($intI -lt $Attempts))
        }
        
        ##### Verify The Credentials Object
        Write-Debug "Verify The Credentials Object";
        If (-not "$($objCredential.UserName)")
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.String]$strEventMessage = "Failed To Enumerate The Credential Object For Exchange EWS.";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Initialize The Credentials Object
        #==================================================================================
        
        #==================================================================================
        #region Enumerate The Destination Mailbox
        #==================================================================================
        
        ##### Verify Whether The User Omitted Mailbox Parameter
        Write-Debug "Verify Whether The User Omitted Mailbox Parameter";
        If (-not "$($Mailbox)")
        {
            ##### Verify Whether The CmdLet Is Run In ActiveDirectory Domain Joined Computer
            Write-Debug "Verify Whether The CmdLet Is Run In ActiveDirectory Domain Joined Computer";
            If ([System.DirectoryServices.DirectoryEntry]::Exists(""))
            {
                ##### Enumerate The LDAP Query
                Write-Debug "Enumerate The LDAP Query";
                [System.String]$strTmp = "";
                [System.String]$strTmp = "$($objCredential.UserName)";
                [System.String]$strTmp = "$(""$($strTmp)"" -replace ""^.+\\"", """")";
                [System.String]$strTmp = "(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName=$($strTmp))(userPrincipalName=$($strTmp))))";
                
                ##### Search The Active Directory About The Specified In The Credential Account
                Write-Verbose "Search The Active Directory About The Specified In The Credential Account";
                [System.DirectoryServices.SearchResultCollection]$arrResults = $null;
                [System.DirectoryServices.SearchResultCollection]$arrResults = Search-SnsAdObject -LdapQuery "$($strTmp)" `
                    -ReturnProperties @("distinguishedName", "proxyAddresses") -ResultSize 2 -GcSearch:$true -Verbose:$false -Debug:$false;
                #####
                
                ##### Verify The Search Output
                Write-Debug "Verify The Search Output";
                If (($arrResults.Count -eq 1) -and ($arrResults[0].Properties.proxyaddresses.Count -gt 0))
                {
                    ##### Enumerate The PrimarySmstpAddress
                    Write-Debug "Enumerate The PrimarySmstpAddress";
                    [System.String]$strTmp = "";
                    [System.String]$strTmp = "$((@($arrResults[0].Properties.proxyaddresses | Where-Object {""$($_)"" -clike ""SMTP:*""} -Verbose:$false -Debug:$false))[0].Replace(""SMTP:"", """").Trim())";
                    
                    ##### Verify Whether The Enumerated PrimarySmstpAddress Match The SMTP RegEx Pattern
                    Write-Debug "Verify Whether The Enumerated PrimarySmstpAddress Match The SMTP RegEx Pattern";
                    If ((-not -not "$($strTmp)") -and ("$($strTmp)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)"))
                    {
                        Write-Verbose "Enumerated Destination Mailbox ""$($strTmp)"".";
                        [System.String]$Mailbox = "$($strTmp)";
                    }
                }
                
                ##### Reset The Variables
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrResults";
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strTmp";
                
                ##### Verify Whether The Destination Mailbox Is Still Not Enumerated
                Write-Debug "Verify Whether The Destination Mailbox Is Still Not Enumerated";
                If ((-not "$($Mailbox)") -and ("$($objCredential.UserName)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)"))
                {
                    ##### Assuming That The UPN Equals The Mailbox PrimarySmstpAddress
                    Write-Verbose "Using The UPN From Credentials As Mailbox.";
                    [System.String]$Mailbox = "$($objCredential.UserName)";
                }
            } Else
            {
                ##### Verify Whether The UserName In The Credentials Object Is UPN
                Write-Debug "Verify Whether The UserName In The Credentials Object Is UPN";
                If ("$($objCredential.UserName)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)")
                {
                    ##### Assuming That The UPN Equals The Mailbox PrimarySmstpAddress
                    Write-Verbose "Using The UPN From Credentials As Mailbox.";
                    [System.String]$Mailbox = "$($objCredential.UserName)";
                }
            }
        }
        
        ##### Verify Whether The Destination Mailbox Is Still Not Enumerated
        Write-Debug "Verify Whether The Destination Mailbox Is Still Not Enumerated";
        If (-not "$($Mailbox)")
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.String]$strEventMessage = "Failed To Enumerate The Destination Mailbox.";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Enumerate The Destination Mailbox
        #==================================================================================
        
        #==================================================================================
        #region Create Exchange EWS Session
        #==================================================================================
        
        ##### Set The Domain In GetNetworkCredential
        Write-Debug "Set The Domain In GetNetworkCredential";
        [System.Net.NetworkCredential]$objNwCred = $null;
        [System.Net.NetworkCredential]$objNwCred = $objCredential.GetNetworkCredential();
        $objNwCred.Domain = "$((""$($objCredential.UserName)"" -replace ""^.+\@"", """") -replace ""\\.+$"","""")";
        
        ##### Loop The Session Creation
        Write-Debug "Loop The Session Creation";
        [System.Int32]$intI = 0;
        [Microsoft.Exchange.WebServices.Data.WebCredentials]$objEwsCred = $null;
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$objExchSrvc = $null;
        Do
        {
            ##### Create WebCredentials Object
            Write-Debug "Create WebCredentials Object";
            [Microsoft.Exchange.WebServices.Data.WebCredentials]$objEwsCred = $null;
            [Microsoft.Exchange.WebServices.Data.WebCredentials]$objEwsCred = New-Object -TypeName "Microsoft.Exchange.WebServices.Data.WebCredentials" `
                -ArgumentList ( $objNwCred.Username, $objNwCred.Password, $objNwCred.Domain ) -Verbose:$false -Debug:$false;
            #####
            
            ##### Create The ExchangeService Object
            Write-Debug "Create The ExchangeService Object";
            [Microsoft.Exchange.WebServices.Data.ExchangeService]$objExchSrvc = $null;
            [Microsoft.Exchange.WebServices.Data.ExchangeService]$objExchSrvc = New-Object -TypeName "Microsoft.Exchange.WebServices.Data.ExchangeService" -Verbose:$false -Debug:$false;
            $objExchSrvc.UseDefaultCredentials = $true;
            $objExchSrvc.Credentials = $objEwsCred;
            
            ##### Establish The EWS Session
            Write-Verbose "Establish The EWS Session";
            $objExchSrvc.AutodiscoverUrl("$($Mailbox)", {$true});
            
            ##### Process The Loop Variable And TimeOut
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
        }
        While ((-not "$($objExchSrvc.Url.AbsoluteUri)") -and ($intI -lt $Attempts))
        
        ##### Verify The EWS Session Creation
        Write-Verbose "Verify The EWS Session Creation";
        If (-not "$($objExchSrvc.Url.AbsoluteUri)")
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.String]$strEventMessage = "Failed To Establish Exchange EWS Session";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Create Exchange EWS Session
        #==================================================================================
        
        ##### Verify The ExchangeService Session Creation
        Write-Debug "Verify The ExchangeService Session Creation";
        If ((-not -not "$($objExchSrvc.Url.AbsoluteUri)") -and (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object {"$($_.Name)" -like 'ObjEwsService'} -Verbose:$false -Debug:$false)))
        {
            ##### Create The Global EWS Service Variable
            Write-Verbose "Create The Global EWS Service Variable ""ObjEwsService""";
            New-Variable -Scope "Global" -Option "Constant" -Name 'ObjEwsService' -Value ($objExchSrvc);
        }
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objEwsCred";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCredential";
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
        
        ##### Continue If Output Is Requested
        Write-Debug "Continue If Output Is Requested";
        If (($PassThru.IsPresent) -and (-not -not "$($objExchSrvc.Url.AbsoluteUri)"))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $objExchSrvc;
        }
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### Connect-SnsExchangeOnline ===================================================
Function Connect-SnsExchangeOnline ()
{
<#
.SYNOPSIS
This CmdLet Establish Remote PowerShell Session To ExchangeOnline In Office 365.
.DESCRIPTION
This CmdLet Establish Remote PowerShell Session To ExchangeOnline In Office 365.
In Case The Session Creation Fail The CmdLet Can Log An Event In The Windows Event Viewer Application Log And Kill
The Script Process. This Functionality Is Enabled Automatically When Value Is Provided To EventSource Parameter.
The CmdLet Have Six Parameter Sets Depending On The Way The CmdLet Authenticates Against ExchangeOnline:
-- FolderPath Here Must Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted
Password File Resides.
-- FilePath Here Must Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To
Generate The UserName From The FileName.
-- Credential Here Must Be Provided System.Management.Automation.PSCredential Object.
-- CertificateThumbprint Here Is Used The PartnerCenter PowerShell Module To Be Generated OAuthV2 AccessToken With
Credential Object Used To Connect To Exchange Online Directly On The Exchange Online PowerShell APIs. In Case The
PartnerCenter PowerShell Module Is Not Installed The CmdLet Will Fail With Error Asking To Install It. The
CertificateThumbprint Parameter Set Uses Microsoft Authentication Libraries (MSAL): https://bit.ly/3x4bhEH. For
That Purpose An Azure App Registration With Service Principal Have To Be Created And The Required Roles To Be
Assigned To The Service Principal As Described Here: https://bit.ly/3FuQRYj
-- ExchangeOnlineManagement Here Is Used The ExchangeOnlineManagement PowerShell Module To Establish A Remote
Session To Exchange Online. In Case The PowerShell Module Is Not Installed On The Machine The CmdLet Will Fail.
For More Details Please Visit https://bit.ly/3CzIs45
-- Interactive It Cannot Be Used Whenever The Script Or The CmdLet Is Executed In As Service Mode.
In This Parameter Set The CmdLet Opens A Window Where The User Can Specify His Credentials.
Obviously When The CmdLet Is Executed As Service There Is No Real Person To Specify The Credentials.
.PARAMETER UserName
Specifies The UserName.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Resides.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using Directory Existence Validation.
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: Yes Using The File Existence And File Extension Validation
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object.
Parameter Set: Credential
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER CertificateThumbprint
Specifies The Thumbprint Of A Certificate Used To Authenticate As Azure ServicePrincipal.
Parameter Set: CertificateThumbprint, ExchangeOnlineManagement
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER ApplicationId
Specifies The ApplicationId Of Azure ServicePrincipal.
Parameter Set: CertificateThumbprint, ExchangeOnlineManagement
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER UserPrincipalName
Specifies The UserPrincipalName Of Azure ServicePrincipal.
Parameter Set: CertificateThumbprint
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER Tenant
Specifies The TenantId.
Parameter Set: CertificateThumbprint
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER Organization
Specifies The Organization Name.
Parameter Set: ExchangeOnlineManagement
Parameter Alias: N/A
Parameter Validation: Yes Using Syntax Validation.
.PARAMETER Interactive
Specifies That The User Have To Be Asked Interactively For Credentials.
Parameter Set: Interactive
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Prefix
Specifies The Prefix For The CmdLets In This Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER ProxyAccessType
Specifies The ProxyAccessType.
The Best Practice Require Direct Internet Access To Office 365.
Accepted Values: "IEConfig", "WinHttpConfig", "AutoDetect", "NoProxyServer", "None".
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using Enumeration Validation
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: All
Parameter Alias: ScriptName
Parameter Validation: N/A
.PARAMETER PassThru
Specifies That The CmdLet Have To Revert A Verification Collection.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Array] Which Contains A List With The ExchangeOnline Accepted Domain Objects.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Array]$arrAcceptedDomains = Connect-SnsExchangeOnline -UserName "john.smith@contoso.com" `
-FolderPath "C:\" -PassThru;
.EXAMPLE
Connect-SnsExchangeOnline -FilePath "C:\john.smith@contoso.com.ini";
.EXAMPLE
Connect-SnsExchangeOnline -Credential $objCredential;
.EXAMPLE
Connect-SnsExchangeOnline -CertificateThumbprint "THISISADEMOTHUMBPRINT" `
-ApplicationId "00000000-0000-0000-0000-000000000000" -UserPrincipalName "ApplicationUPN@contoso.onmicrosoft.com" `
-Tenant "00000000-0000-0000-0000-000000000000";
.EXAMPLE
Connect-SnsExchangeOnline -Organization "contoso.onmicrosoft.com" `
-CertificateThumbprint "012THISISADEMOTHUMBPRINT" -ApplicationId "00000000-0000-0000-0000-000000000000";
.EXAMPLE
Connect-SnsExchangeOnline -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "Interactive")]
Param (
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("CredentialFile")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Parameter(Mandatory = $true, ParameterSetName = "ExchangeOnlineManagement", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$CertificateThumbprint,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Parameter(Mandatory = $true, ParameterSetName = "ExchangeOnlineManagement", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$ApplicationId,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$UserPrincipalName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "CertificateThumbprint", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$Tenant,
    
    [Parameter(Mandatory = $true, ParameterSetName = "ExchangeOnlineManagement", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".onmicrosoft.com"))})]
    [ValidateNotNullOrEmpty()][System.String]$Organization,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$Prefix,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateSet("IEConfig", "WinHttpConfig", "AutoDetect", "NoProxyServer", "None")]
    [ValidateNotNullOrEmpty()][System.String]$ProxyAccessType = "NoProxyServer",
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 3,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ScriptName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({([SnsPsModule.SnsEventLog]::VerifySnsEventSource("$($_)"))})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$PassThru
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Connect-SnsExchangeOnline";
        Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        If ("$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"" -Verbose:$false -Debug:$false).Hash)" -ne "$($global:SnsModuleCfg.ModuleHash)")
        { Write-Warning "There Is New Version Of SnsPsModule Module Released. Please Restart The PowerShell Session." -WarningAction "Continue"; };
        
        ##### Generate The Verbose Boolean
        Write-Debug "Generate The Verbose Boolean";
        [System.Boolean]$bolVerbose = $false
        If ($PSCmdlet.MyInvocation.BoundParameters.Keys -icontains "Verbose") { [System.Boolean]$bolVerbose = $PSCmdlet.MyInvocation.BoundParameters.Verbose.IsPresent; }
        
        ##### Initialize The Variables
        [System.String]$strInitialVerboseSettings = "$($VerbosePreference)";
        [System.Management.Automation.ScriptBlock]$scrBlock = $null;
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        [System.Array]$arrAcceptedDomains = @();
        
        ##### Generate The Verification Script Block
        [System.Management.Automation.ScriptBlock]$scrBlock = [System.Management.Automation.ScriptBlock]::Create("Get-$($Prefix)AcceptedDomain");
        
        #==================================================================================
        #region Initialize The Credentials Object
        #==================================================================================
        
        ###### Generate The Credential Object In FolderPath Parameter Set
        Write-Debug "Verify The Parameter Set Name";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "ExchangeOnlineManagement"
            {
                Write-Debug "Load ExchangeOnlineManagement PowerShell Module";
                If (Import-SnsModuleHelper -Module "ExchangeOnlineManagement" -EventSource "$($EventSource)") { Return; }
                Break;
            }
            
            "Interactive"
            {
                Write-Debug "Load ExchangeOnlineManagement PowerShell Module";
                If (Import-SnsModuleHelper -Module "ExchangeOnlineManagement" -EventSource "$($EventSource)") { Return; }
                Break;
            }
            
            "FilePath"
            {
                Write-Verbose "Import The Credential From A File";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -FilePath "$($FilePath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Break;
            }
            
            "FolderPath"
            {
                Write-Verbose "Import The Credential From A File";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -UserName "$($UserName)" -FolderPath "$($FolderPath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Break;
            }
            
            "Credential"
            {
                Write-Verbose "Assign The Provided Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = $Credential;
                Break;
            }
            
            "CertificateThumbprint"
            {
                ##### Load PartnerCenter PowerShell Module
                Write-Debug "Load PartnerCenter PowerShell Module";
                If (Import-SnsModuleHelper -Module "PartnerCenter" -EventSource "$($EventSource)") { Return; }
                
                ##### Initialize AccessToken With The Specified Service Principal Certificate
                Write-Verbose "Initialize AccessToken With The Specified Service Principal Certificate";
                [Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication.AuthResult]$objToken = $null;
                [Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication.AuthResult]$objToken = New-PartnerAccessToken -ApplicationId "$($ApplicationId)" `
                    -CertificateThumbprint "$($CertificateThumbprint)" -Scopes "https://outlook.office365.com/.default" -ServicePrincipal:$true -Tenant "$($Tenant)" -Verbose:$false -Debug:$false;
                #####
                
                ##### Initialize PSCredential Object With The Enumerated AccessToken
                Write-Verbose "Initialize PSCredential Object With The Enumerated AccessToken";
                [System.Management.Automation.PSCredential]$objCredential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList @(
                    "$($UserPrincipalName)",
                    $(ConvertTo-SecureString "Bearer $($objToken.AccessToken)" -AsPlainText:$true -Force:$true -Verbose:$false -Debug:$false)
                );
                
                ##### Reset The Variables
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objToken";
                
                Break;
            }
        }
        
        ##### Verify The Credential Object
        Write-Debug "Verify The Credential Object";
        If (("$($PSCmdlet.ParameterSetName)" -ne "ExchangeOnlineManagement") -and ("$($PSCmdlet.ParameterSetName)" -ne "Interactive") -and (-not "$($objCredential.UserName)"))
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.String]$strEventMessage = "Failed To Enumerate The Credential For ExchangeOnline";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Initialize The Credentials Object
        #==================================================================================
        
        #==================================================================================
        #region Establish The ExchangeOnline Remote PSSession
        #==================================================================================
        
        ##### Remove Any Previous Sessions
        Write-Debug "Remove Any Previous Sessions";
        Remove-SnsPreviousSessionsHelper -Filter "ExchangeOnlineInternalSession" -ScrBlock $scrBlock -Verbose:$bolVerbose -Debug:$false;
        
        ##### Verify The ParameterSetName
        Write-Debug "Verify The ParameterSetName";
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        $VerbosePreference = "SilentlyContinue";
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "ExchangeOnlineManagement"
            {
                [System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsExoSessionMsModuleHelper -Organization "$($Organization)" -ProxyAccessType "$($ProxyAccessType)" `
                    -CertificateThumbprint "$($CertificateThumbprint)" -ApplicationId "$($ApplicationId)" -Prefix "$($Prefix)" -Attempts $Attempts -Verbose:$bolVerbose -Debug:$false;
                Break;
            }
            
            "Interactive"
            {
                [System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsExoSessionInteractiveHelper -Prefix "$($Prefix)" `
                    -Attempts $Attempts -ProxyAccessType "$($ProxyAccessType)" -Verbose:$bolVerbose -Debug:$false;
                Break;
            }
            
            default
            {
                [System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsRemoteSessionNoModuleHelper -Credential $objCredential `
                    -Url "$(If (-not -not ""$($CertificateThumbprint)"") { ""https://ps.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true""; } Else { ""https://ps.outlook.com/powershell""; })" `
                    -Prefix "$($Prefix)" -ProxyAccessType "$($ProxyAccessType)" -Attempts $Attempts -EventSource "$($EventSource)" -Verbose:$bolVerbose -Debug:$false;
                #####
            }
        }
        
        $VerbosePreference = "$($strInitialVerboseSettings)";
        
        #==================================================================================
        #endregion Establish The ExchangeOnline Remote PSSession
        #==================================================================================
        
        #==================================================================================
        #region Retrieve The Accepted Domains Via The Remote Session
        #==================================================================================
        
        ##### Verify The PSSession Import
        Write-Debug "Verify The PSSession Import";
        If (-not -not "$($objSesModule.Name)")
        {
            ##### Verify Whether Get-AcceptedDomain Command Is Among The Exported Commands
            Write-Debug "Verify Whether Get-AcceptedDomain Command Is Among The Exported Commands";
            If ($objSesModule.ExportedCommands.Keys -icontains "Get-$($Prefix)AcceptedDomain")
            {
                ##### Generate The ExchangeOnline Accepted Domain Array
                Write-Debug "Generate The ExchangeOnline Accepted Domain Array";
                [System.Array]$arrAcceptedDomains = @();
                [System.Array]$arrAcceptedDomains = Invoke-Command -ScriptBlock $scrBlock -Verbose:$false -Debug:$false;
                
                ##### Verify The ExchangeOnline Accepted Domain Array
                Write-Debug "Verify The ExchangeOnline Accepted Domain Array";
                If (($arrAcceptedDomains.Count) -eq 0)
                {
                    [System.String]$strEventMessage = "Failed To Retrieve The ExchangeOnline AcceptedDomain";
                    Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                    Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                    Return;
                }
            }
            Else
            {
                Return;
            }
        }
        
        #==================================================================================
        #endregion Retrieve The Accepted Domains Via The Remote Session
        #==================================================================================
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objSesModule";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCredential";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "scrBlock";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strInitialVerboseSettings";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolVerbose";
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
        
        ##### Continue If Output Is Requested
        Write-Debug "Continue If Output Is Requested";
        If (($PassThru.IsPresent) -and ($arrAcceptedDomains.Count -gt 0))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $arrAcceptedDomains;
        }
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### Connect-SnsExchangeOnPremises ===============================================
Function Connect-SnsExchangeOnPremises ()
{
<#
.SYNOPSIS
Establishes A Remote PowerShell Session To Exchange On Premises CAS Server.
.DESCRIPTION
Establishes A Remote PowerShell Session To Exchange On Premises CAS Server.
In Case The Session Creation Fail The CmdLet Can Log An Event In The Windows Event Viewer Application Log And Kill
The Script Process. This Functionality Is Enabled Automatically When Value Is Provided To EventSource Parameter.
The CmdLet Have Four Parameter Sets Depending On The Way That The CmdLet Authenticates Against The CAS Server:
-- FolderPath Here Must Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted
Password File Resides.
-- FilePath Here Must Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To
Enumerate The UserName From The FileName.
-- Credential Here Must Be Provided System.Management.Automation.PSCredential Object.
-- Interactive It Cannot Be Used Whenever The Script Or The Function Is Executed In As Service Mode.
In This Parameter Set The CmdLet Opens A Window Where The User Can Specify His Credentials.
Obviously When The CmdLet Is Executed As Service There Is No Real Person To Specify The Credentials.
.PARAMETER HostName
Specifies The Fully Qualified Domain Name Of An Exchange CAS Server Or a CAS Array.
In Case The CAS Array Is Created With Hardware Load Balancer And The PowerShell Virtual Directory Is Not
Published There The Connection Will Fail In This Scenario The Only Way To Connect Is Using CAS Server FQDN.
If Omitted The CmdLet Will Try To Retrieve The CAS Servers From AD And Chose The Nearest One If ICMP Protocol
Is Enabled In The Environment. If The ICMP Is Not Enabled The CmdLet Will Take The Last Server From The List
Where The Enumerated CAS Servers Are Sorted Alphabetically.
In Order The Automatic CAS Servers Enumeration To Work As Expected The Exchange Must Be Installed In The Root
AD Domain. In Single Domain Environments It Always Work.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using DNS System Validation
.PARAMETER UserName
Specifies The UserName.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Resides.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using Folder Existence Validation
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: Yes Using File Existence Validation
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object.
Parameter Set: Credential
Parameter Alias: N/A
Parameter Validation: Yes Using Impersonated LDAP Query Validation
.PARAMETER Interactive
Specifies That The User Have To Be Asked Interactively For Credentials.
Parameter Set: Interactive
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Protocol
Specifies Whether HTTP Or HTTPS Protocol Shall Be Used.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using Enumeration Validation
.PARAMETER Prefix
Specifies The Prefix For The CmdLets In This Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: All
Parameter Alias: ScriptName
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER PassThru
Specifies That The CmdLet Have To Revert A Verification Collection.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Array] Which Contains A List With The On Premises Accepted Domain Objects.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Array]$arrAcceptedDomains = Connect-SnsExchangeOnPremises -UserName "CONTOSO\john.smith" `
-FolderPath "C:\" -PassThru;
.EXAMPLE
Connect-SnsExchangeOnPremises -FilePath "C:\CONTOSO@@john.smith.ini";
.EXAMPLE
Connect-SnsExchangeOnPremises -Credential $objCredential;
.EXAMPLE
Connect-SnsExchangeOnPremises -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[Alias("New-ExchangeOnPremSession")]
[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "Interactive")]
Param (
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({(-not -not "$([Net.DNS]::GetHostEntry(""$($_)"").HostName)")})]
    [AllowNull()][AllowEmptyString()][System.String]$HostName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({(("$($_)".Contains("\")) -or ("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)"))})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("CredentialFile")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateSet('https','http')]
    [ValidateNotNullOrEmpty()][System.String]$Protocol = 'https',
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowNull()][AllowEmptyString()][System.String]$Prefix,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 3,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ScriptName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({([SnsPsModule.SnsEventLog]::VerifySnsEventSource("$($_)"))})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$PassThru
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Connect-SnsExchangeOnPremises";
        Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)`r`n";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        If ("$((Get-FileHash -Path ""$($PSCommandPath)"" -Algorithm ""SHA256"" -Verbose:$false -Debug:$false).Hash)" -ne "$($global:SnsModuleCfg.ModuleHash)")
        { Write-Warning "There Is New Version Of SnsPsModule Module Released. Please Restart The PowerShell Session." -WarningAction "Continue"; };
        
        ##### Generate The Verbose Boolean
        Write-Debug "Generate The Verbose Boolean";
        [System.Boolean]$bolVerbose = $false
        If ($PSCmdlet.MyInvocation.BoundParameters.Keys -icontains "Verbose") { [System.Boolean]$bolVerbose = $PSCmdlet.MyInvocation.BoundParameters.Verbose.IsPresent; }
        
        ##### Initialize The Variables
        [System.String]$strInitialVerboseSettings = "$($VerbosePreference)";
        [System.Management.Automation.ScriptBlock]$scrBlock = $null;
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Int32]$intI = 0;
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        [System.Array]$arrAcceptedDomains = @();
        
        ##### Generate The Verification Script Block
        [System.Management.Automation.ScriptBlock]$scrBlock = [System.Management.Automation.ScriptBlock]::Create("Get-$($Prefix)AcceptedDomain");
        
        #==================================================================================
        #region Enumerate The Session Target
        #==================================================================================
        
        ##### Verify Whether HostName Is Not Provided
        Write-Debug "Verify Whether HostName Is Not Provided";
        If (-not "$($HostName)") { [System.String]$HostName = Get-SnsNearestAliveCasServerHelper -Verbose:$bolVerbose -Debug:$false; }
        
        ##### Verify The Target Server Generation
        Write-Debug "Verify The Target Server Generation";
        If (-not "$($HostName)")
        {
            [System.String]$strEventMessage = "There Is No Destination HostName Provided";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Enumerate The Session Target
        #==================================================================================
        
        #==================================================================================
        #region Initialize The Credentials Object
        #==================================================================================
        
        ###### Verify The Parameter Set Name
        Write-Debug "Verify The Parameter Set Name";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "FilePath"
            {
                Write-Verbose "Import The Credential From A File";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -FilePath "$($FilePath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                If (-not (Test-SnsCredentialHelper -Credential $objCredential -Verbose:$bolVerbose -Debug:$false)) { [System.Management.Automation.PSCredential]$objCredential = $null; }
                Break;
            }
            
            "FolderPath"
            {
                Write-Verbose "Import The Credential From A File";
                [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -UserName "$($UserName)" -FolderPath "$($FolderPath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                If (-not (Test-SnsCredentialHelper -Credential $objCredential -Verbose:$bolVerbose -Debug:$false)) { [System.Management.Automation.PSCredential]$objCredential = $null; }
                Break;
            }
            
            "Credential"
            {
                Write-Verbose "Assign The Provided Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = $Credential;
                If (-not (Test-SnsCredentialHelper -Credential $objCredential -Verbose:$bolVerbose -Debug:$false)) { [System.Management.Automation.PSCredential]$objCredential = $null; }
                Break;
            }
        }
        
        ##### Verify If It Is Interactive Session And There Are No Credentials
        Write-Debug "Verify If It Is Interactive Session And There Are No Credentials";
        If ([System.Environment]::UserInteractive)
        {
            ##### Loop Interactive Credentials Dialog With The User
            Write-Debug "Loop Interactive Credentials Dialog With The User";
            [System.Int32]$intI = 0;
            While ((-not "$($objCredential.UserName)") -and ($intI -lt $Attempts))
            {
                ##### Ask The User About Credentials
                Write-Verbose "Ask The User About Credentials";
                [System.Management.Automation.PSCredential]$objCredential = $null;
                [System.Management.Automation.PSCredential]$objCredential = Get-Credential -Verbose:$false -Debug:$false;
                
                ##### Verify The Provided Credentials
                Write-Verbose "Verify The Provided Credentials";
                If (-not (Test-SnsCredentialHelper -Credential $objCredential -Verbose:$bolVerbose -Debug:$false)) { [System.Management.Automation.PSCredential]$objCredential = $null; }
                
                ##### Process The Loop Variable And TimeOut
                Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
            }
        }
        
        ##### Verify The Credentials Object
        Write-Debug "Verify The Credentials Object";
        If (-not "$($objCredential.UserName)")
        {
            [System.String]$strEventMessage = "Failed To Enumerate Exchange On Premises Credential";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return;
        }
        
        #==================================================================================
        #endregion Initialize The Credentials Object
        #==================================================================================
        
        #==================================================================================
        #region Create Exchange On Premises Session
        #==================================================================================
        
        ##### Remove Any Previous Sessions
        Write-Debug "Remove Any Previous Sessions";
        Remove-SnsPreviousSessionsHelper -Filter "$($HostName)" -ScrBlock $scrBlock -Verbose:$bolVerbose -Debug:$false;
        
        ##### Verify The Prerequisites For Remote PSSession
        Write-Debug "Verify The Prerequisites For Remote PSSession";
        If ((-not -not "$($HostName)") -and (-not -not "$($objCredential.UserName)"))
        {
            ##### Verify The ParameterSetName
            Write-Debug "Verify The ParameterSetName";
            $VerbosePreference = "SilentlyContinue";
            [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
            [System.Management.Automation.PSModuleInfo]$objSesModule = New-SnsRemoteSessionNoModuleHelper -Credential $objCredential -Url "$($Protocol)://$($HostName)/PowerShell/" `
                -Prefix "$($Prefix)" -ProxyAccessType "NoProxyServer" -Attempts $Attempts -EventSource "$($EventSource)" -Verbose:$bolVerbose -Debug:$false;
            $VerbosePreference = "$($strInitialVerboseSettings)";
        }
        
        #==================================================================================
        #endregion Create Exchange On Premises Session
        #==================================================================================
        
        #==================================================================================
        #region Retrieve The Accepted Domains Via The Remote Session
        #==================================================================================
        
        ##### Verify The PSSession Import
        Write-Debug "Verify The PSSession Import";
        If (-not -not "$($objSesModule.Name)")
        {
            ##### Verify Whether Get-AcceptedDomain Command Is Among The Exported Commands
            Write-Debug "Verify Whether Get-AcceptedDomain Command Is Among The Exported Commands";
            If ($objSesModule.ExportedCommands.Keys -icontains "Get-$($Prefix)AcceptedDomain")
            {
                ##### Generate The On Premises Accepted Domain Array
                Write-Debug "Generate The On Premises Accepted Domain Array";
                [System.Array]$arrAcceptedDomains = @();
                [System.Array]$arrAcceptedDomains = Invoke-Command -ScriptBlock $scrBlock -Verbose:$false -Debug:$false;
                
                ##### Verify The Exchange On Premises Session Import
                Write-Debug "Verify The Exchange On Premises Session Import";
                If (($arrAcceptedDomains.Count) -eq 0)
                {
                    [System.String]$strEventMessage = "Failed To Retrieve The Exchange On Premises AcceptedDomain";
                    Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                    Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                    Return;
                }
            }
            Else
            {
                Return;
            }
        }
        
        #==================================================================================
        #endregion Retrieve The Accepted Domains Via The Remote Session
        #==================================================================================
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objSesModule";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCredential";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "scrBlock";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strInitialVerboseSettings";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolVerbose";
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
        
        ##### Continue If Output Is Requested
        Write-Debug "Continue If Output Is Requested";
        If (($PassThru.IsPresent) -and ($arrAcceptedDomains.Count -gt 0))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $arrAcceptedDomains
        }
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
}

##### Connect-SnsMsolService ======================================================
Function Connect-SnsMsolService ()
{
<#
.SYNOPSIS
Establishes A Remote PowerShell Session To AzureAD MSOnline V1 Service.
.DESCRIPTION
Establishes A Remote PowerShell Session To AzureAD MSOnline V1 Service.
The CmdLet Have Four Parameter Sets Depending On The Ways It Authenticates Against AzureAD:
-- FolderPath Here Must Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted
Password File Resides.
-- FilePath Here Must Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To
Enumerate The UserName From The FileName.
-- Credential Here Must Be Provided System.Management.Automation.PSCredential Object.
-- Interactive This Is The Only Parameter Set Which Is Capable To Establish Remote PowerShell Session To MSOnline
V1 Service With Multifactor Authentication. However It Cannot Be Used Whenever The Script Or The Function Is
Executed In As Service Mode.
In This Parameter Set The CmdLet Opens A Window Where The User Can Specify His Credentials And Multi Factor
Authentication Code Received On A SMS Or Inside A Phone App Or Phone Call And Etc.
Obviously When The CmdLet Is Executed As Service There Is No Real Person To Specify The Credentials And The MFA
Code. Depending On The Configuration There Might Not Be PowerShell Host Console Window Either.
NOTE: The CmdLet Requires MSOnline Module To Be Installed In Advance. Please Refer To https://bit.ly/2O0VxwO
NOTE: There Must Be A Direct Connection / Firewall Openings To Office 365. Proxy Usage Is Not Allowed.
.PARAMETER UserName
Specifies The UserName In UPN Format.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Resides.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using Folder Existence Validation
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: Yes Using File Existence And File Extension Validation
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object.
Parameter Set: Credential
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER Interactive
Specifies That The User Have To Be Asked Interactively For Credentials.
Parameter Set: Interactive
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Establish The Remote Session.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER EventSource
Specifies The Application Log Event Source To Be Used For The Error Event Logging.
Parameter Set: All
Parameter Alias: ScriptName
Parameter Validation: Yes Using The EventSource Existence Validation
.PARAMETER PassThru
Specifies That The CmdLet Have To Revert A Verification Collection.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input None
.OUTPUTS
[System.Array] Which Contains A List With The Available Msol License Objects.
.NOTES
AUTHOR: Svetoslav Nedyalkov Savov
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
.EXAMPLE
[System.Array]$arrAzureLicenses = Connect-SnsMsolService -UserName "john.smith@contoso.com" `
-FolderPath "C:\" -PassThru;
.EXAMPLE
Connect-SnsMsolService -FilePath "C:\john.smith@contoso.com.ini";
.EXAMPLE
Connect-SnsMsolService -Credential $objCredential;
.EXAMPLE
Connect-SnsMsolService -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "Interactive")]
Param (
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::SnsSmtpAddressPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("CredentialFile")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()]