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()][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-SnsMsolService";
        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.Management.Automation.PSCredential]$objCredential = $null;
        [System.Int32]$intI = 0;
        [System.Array]$arrClLics = @();
        
        #==================================================================================
        #region Enumerate The Credentials Object
        #==================================================================================
        
        ##### Load MSOnline PowerShell Module
        Write-Debug "Load MSOnline PowerShell Module";
        If (Import-SnsModuleHelper -Module "MSOnline" -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)")
        {
            "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 ((-not "$($objCredential.UserName)") -and (-not [System.Environment]::UserInteractive))
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.String]$strEventMessage = "Failed To Enumerate The Msol V1 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 Establish Msol Service Connection
        #==================================================================================
        
        ##### Loop The Session Creation
        Write-Debug "Loop The Session Creation";
        [System.Int32]$intI = 0;
        [System.Array]$arrClLics = @();
        Do
        {
            ##### Verify The ParameterSetName
            Write-Debug "Verify The ParameterSetName";
            If ((-not "$($objCredential.UserName)") -and [System.Environment]::UserInteractive)
            {
                ##### Display The Action On The Console
                Write-Verbose "Interactively Connecting To Msol V1 Service.";
                Connect-MsolService | Out-Null;
            }
            Else
            {
                If (-not -not "$($objCredential.UserName)")
                {
                    ##### Display The Action On The Console
                    Write-Verbose "Connecting To Msol V1 Service.";
                    Connect-MsolService -Credential $objCredential | Out-Null;
                }
                Else
                {
                    [System.Management.Automation.PSCredential]$objCredential = $null;
                    [System.String]$strEventMessage = "Failed To Enumerate The Msol V1 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;
                }
            }
            
            ##### Process The Loop Variable and TimeOut
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
            
            ##### Verify The PowerShell Session To The Msol Service
            [System.Array]$arrClLics = @();
            [System.Array]$arrClLics = Get-MsolAccountSku -Verbose:$false -Debug:$false | `
                Select-Object -Verbose:$false -Debug:$false @("AccountSkuId", "SkuId", "ServiceStatus", "ActiveUnits", "ConsumedUnits");
            #####
        }
        While (($arrClLics.Count -lt 1) -and ($intI -lt $Attempts))
        
        ##### Verify The Msol V1 Service Session Creation
        Write-Debug "Verify The Msol V1 Service Session Creation";
        If ($arrClLics.Count -lt 1)
        {
            [System.String]$strEventMessage = "Failed To Establish A Connection To Msol V1 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 Msol V1 Licenses Variable Exists
            Write-Debug "Verify Whether The Global Msol V1 Licenses Variable Exists";
            If (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object {"$($_.Name)" -like "ArrMsolLicenses"} -Verbose:$false -Debug:$false))
            {
                ##### Initialize The Global Msol V1 Licenses Variable
                Write-Debug "Create The Global Msol V1 Licenses Variable";
                New-Variable -Scope "Global" -Option "Constant" -Name "ArrMsolLicenses" -Value ($arrClLics);
            }
        }
        
        #==================================================================================
        #endregion Establish Msol 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 "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-SnsSharePointOnline =================================================
Function Connect-SnsSharePointOnline ()
{
<#
.SYNOPSIS
Establishes Remote PowerShell Session To SharePoint Online In Office 365.
.DESCRIPTION
Establishes Remote PowerShell Session To SharePoint Online 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 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.
NOTE: The CmdLet Requires Microsoft.Online.SharePoint.PowerShell Module To Be Installed In Advance
https://bit.ly/315AQoE
NOTE: The CmdLet Requires The Host To Be Prepared For Remote PowerShell With Enable-PSRemoting And Then
Disable-PSRemoting. The Host Does Not Require To Accept Remote Sessions. However Without Preparing And Then
Removing The Remote Sessions Accepting The Generation Of Remote Sessions To Other Hosts Does Not Work As Well.
.PARAMETER UserName
Specifies The UserName.
Parameter Set: FolderPath And Interactive
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 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 AuthenticationUrl
Location For AAD Cross-Tenant Authentication Service. Can Be Optionally Used If Non-Default Cross-Tenant
Authentication Service Is Used.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using DNS System Validation
.PARAMETER ClientTag
Permits Appending A Client Tag To Existing Client Tag. Used Optionally In The CSOM http Traffic To Identify
Used Script Or Solution.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER Region
Specifies The Office 365 Regional Instances.
Accepted Values: "Default", "ITAR", "Germany", "China"
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes, Using Enumeration Validation.
.PARAMETER TenantName
Specifies The Office 365 Tenant Name To Which must Be Established A Connection.
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.Object] Which Contains SharePoint Tenant Object.
.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]$objSpTenant = Connect-SnsSharePointOnline -UserName "john.smith@contoso.com" `
-FolderPath "C:\" -PassThru;
.EXAMPLE
Connect-SnsSharePointOnline -FilePath "C:\john.smith@contoso.com.ini";
.EXAMPLE
Connect-SnsSharePointOnline -Credential $objCredential;
.EXAMPLE
Connect-SnsSharePointOnline -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)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({(-not -not "$([Net.DNS]::GetHostEntry(""$(""$($_)"".Split(""/"")[2])"").HostName)")})]
    [ValidateNotNullOrEmpty()][System.String]$AuthenticationUrl,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$ClientTag,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateSet("Default", "ITAR", "Germany", "China")]
    [ValidateNotNullOrEmpty()][System.String]$Region = "Default",
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$TenantName,
    
    [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-SnsSharePointOnline";
        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.Management.Automation.ScriptBlock]$scrBlock = $null;
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        [System.Int32]$intI = 0;
        [System.Object]$objSpTenant = $null;
        
        ##### Initialize The Verification Script Block
        [System.Management.Automation.ScriptBlock]$scrBlock = [System.Management.Automation.ScriptBlock]::Create("Get-SPOTenant");
        
        #==================================================================================
        #region Initialize The Credential Object
        #==================================================================================
        
        ##### Load Microsoft.Online.SharePoint.PowerShell Module
        Write-Debug "Load Microsoft.Online.SharePoint.PowerShell Module";
        If (Import-SnsModuleHelper -Module "Microsoft.Online.SharePoint.PowerShell" -EventSource "$($EventSource)") { Return; }
        
        ###### 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;
                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;
            }
        }
        
        ##### Verify The Credential Object
        Write-Debug "Verify The Credential Object";
        If ((-not [System.Environment]::UserInteractive) -and (-not "$($objCredential.UserName)"))
        {
            [System.String]$strEventMessage = "Failed To Enumerate The SharePoint Online 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 Credential Object
        #==================================================================================
        
        #==================================================================================
        #region Create SharePoint Online Session
        #==================================================================================
        
        ##### 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 "ObjSpoTenant"}))
        {
            ##### Disconnect The Previous Sessions
            Write-Verbose "Removing Previous Sessions"; 
            Disconnect-SPOService -Verbose:$false -Debug:$false | Out-Null;
        }
        
        ##### Prepare The Host Machine For Remote PSSession
        Write-Debug "Prepare The Host Machine For Remote PSSession";
        If (-not (Prepare-SnsHostForRemoteSessionsHelper "$($EventSource)" -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false)) { Return; }
        
        ##### Generate The Splatting HashTable
        Write-Debug "Generate The Splatting HashTable For New-CsOnlineSession";
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        If (-not -not "$($objCredential.UserName)") { $hshSplat.Add("Credential", $objCredential); }
        If (-not -not "$($AuthenticationUrl)") { $hshSplat.Add("AuthenticationUrl", "$($AuthenticationUrl)"); }
        If (-not -not "$($ClientTag)") { $hshSplat.Add("ClientTag", "$($ClientTag)"); }
        If (-not -not "$($Region)") { $hshSplat.Add("Region", "$($Region)"); }
        $hshSplat.Add("Url", "https://$($TenantName)-admin.sharepoint.com");
        $hshSplat.Add("Verbose", $false);
        $hshSplat.Add("Debug", $false);
        
        ##### Loop The Session Creation
        Write-Debug "Loop The Session Creation";
        [System.Int32]$intI = 0;
        [System.Object]$objSpTenant = $null;
        Do
        {
            ##### Establish The SharePoint Online Session
            Write-Verbose "Establish The SharePoint Online Session";
            Connect-SPOService @hshSplat | Out-Null;
            
            ##### Process The Loop Variable And TimeOut
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
            
            ##### Verify The Session Establishing
            [System.Object]$objSpTenant = $null;
            [System.Object]$objSpTenant = Invoke-Command -ScriptBlock $scrBlock -Verbose:$false -Debug:$false;
        }
        While ((-not "$($objSpTenant.SharingCapability)") -and ($intI -lt $Attempts))
        
        ##### Verify Session Creation
        Write-Debug "Verify Session Creation";
        If (-not "$($objSpTenant.SharingCapability)")
        {
            [System.String]$strEventMessage = "Failed To Establish PowerShell Session To SharePoint Online";
            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 SharePoint Online Session
        #==================================================================================
        
        #==================================================================================
        #region Generate The Output Objects
        #==================================================================================
        
        ##### Verify The PSSession Import
        Write-Debug "Verify The PSSession Import";
        If (-not -not "$($objSpTenant.SharingCapability)")
        {
            ##### Verify Whether The Global SharePoint Online Tenant Variable Exists
            Write-Debug "Verify Whether The Global SharePoint Online Tenant Variable Exists";
            If (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object {"$($_.Name)" -like "ObjSpoTenant"} -Verbose:$false -Debug:$false))
            {
                ##### Create The Global SharePoint Online Tenant Variable
                Write-Debug "Create The Global SharePoint Online Tenant Variable";
                New-Variable -Scope "Global" -Option "Constant" -Name "ObjSpoTenant" -Value ($objSpTenant);
            }
        }
        
        #==================================================================================
        #endregion Retrieve The SIP Domains Via The Remote Session
        #==================================================================================
        
        ##### 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";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "scrBlock";
    }
    
    ##### 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 "$($objSpTenant.SharingCapability)"))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $objSpTenant;
        }
    }
}

##### Connect-SnsSkypeOnPremises ==================================================
Function Connect-SnsSkypeOnPremises ()
{
<#
.SYNOPSIS
Establishes Remote PowerShell Session To Skype For Business On Premises.
.DESCRIPTION
Establishes Remote PowerShell Session To Skype For Business On Premises.
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 Registrar
Specifies The Fully Qualified Domain Name Of A Front End Server Or A Registrar Pool.
It Will Be Used As PSSession Remote Host. As A Best Practice SBA Must Be Avoided Because They Cannot Manage
The PSSession Load.
If Omitted The CmdLet Will Try To Retrieve The FrontEnd Pools From AD And Chose The Nearest One.
The Automatic Retrieval Works Only When The Skype Is Installed In The Root AD Domain. In Single Domain Forests
It Always Works.
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: 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 SkipSkypeModuleCheck
Force The CmdLet To Skip The SkypeForBusiness PowerShell Module Existence Verification.
If Omitted The CmdLet Will Try To Detect Whether SkypeForBusiness Module Is Installed And Imported. In Case It
Is Installed And Not Imported The CmdLet Will Import It.
Whenever SkypeForBusiness Module Is Available There Is No Need Of Skype On Premises Remote PSSession. The
CmdLet Is Intended To Be Used Only When Skype For Business Management Tools Are Not Installed.
Parameter Set: All
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 Skype For Business On Premises SIP 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]$arrSipDomains = Connect-SnsSkypeOnPremises -UserName "CONTOSO\john.smith" `
-FolderPath "C:\" -PassThru;
.EXAMPLE
Connect-SnsSkypeOnPremises -FilePath "C:\CONTOSO@@john.smith.ini";
.EXAMPLE
Connect-SnsSkypeOnPremises -Credential $objCredential;
.EXAMPLE
Connect-SnsSkypeOnPremises -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[Alias("New-SfbOnPremSession")]
[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]$Registrar,
    
    [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)]
    [Switch]$SkipSkypeModuleCheck = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [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-SnsSkypeOnPremises";
        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.Net.NetworkInformation.Ping]$objPing = $null;
        [System.Management.Automation.ScriptBlock]$scrBlock = $null;
        [System.Array]$arrSipDomains = @();
        [System.Array]$arrFrontEndPools = @();
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Management.Automation.PSModuleInfo]$objSesModule = $null;
        
        ##### Generate The Ping Object
        Write-Debug "Generate The Ping Object";
        [System.Net.NetworkInformation.Ping]$objPing = $null;
        [System.Net.NetworkInformation.Ping]$objPing = New-Object -TypeName 'System.Net.NetworkInformation.Ping' -Verbose:$false -Debug:$false;
        
        ##### Generate The Verification Script Block
        [System.Management.Automation.ScriptBlock]$scrBlock = $null;
        [System.Management.Automation.ScriptBlock]$scrBlock = [System.Management.Automation.ScriptBlock]::Create("Get-$($Prefix)CsSipDomain");
        
        #==================================================================================
        #region Verify If The SkypeForBusiness Module Is Available And Loaded
        #==================================================================================
        
        If ((-not $SkipSkypeModuleCheck.IsPresent) -and (-not -not (Get-Module -Name "SkypeForBusiness" -Verbose:$false -Debug:$false)))
        {
            ##### Generate The Warning Message
            [System.String]$strEventMessage = "";
            [System.String]$strEventMessage += "`r`n$(""*"" * 100)`r`n*$("" "" * 98)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 27)Module ""SkypeForBusiness"" Is Already Loaded.$("" "" * 27)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 98)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 23)There Is No Need Of Remote SkypeForBusiness Session.$("" "" * 23)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 98)*`r`n$(""*"" * 100)`r`n`r`n";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Warning" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            Return (Get-CsSipDomain -Verbose:$false -Debug:$false)
        }
        
        ##### Verify SkypeForBusiness PowerShell Module Load
        Write-Debug "Verify SkypeForBusiness PowerShell Module Load";
        If ((-not $SkipSkypeModuleCheck.IsPresent) -and (-not -not (Get-Module -Name "SkypeForBusiness" -ListAvailable:$true -Verbose:$false -Debug:$false)))
        {
            ##### Generate The Warning Message
            [System.String]$strEventMessage = "";
            [System.String]$strEventMessage += "`r`n$(""*"" * 100)`r`n*$("" "" * 98)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 20)Module ""SkypeForBusiness"" Is Already Present On The Server$("" "" * 20)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 98)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 34)Please Load The Module Instead$("" "" * 34)*`r`n";
            [System.String]$strEventMessage += "*$("" "" * 98)*`r`n$(""*"" * 100)`r`n`r`n";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Warning" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
            
            ##### Import SkypeForBusiness PowerShell Module
            If (-not (Get-Module -Name "SkypeForBusiness" -Verbose:$false -Debug:$false))
            {
                Write-Host "Import ""SkypeForBusiness"" PowerShell Module" -ForegroundColor "Green";
                [System.String]$strVerbosePreference = "$($VerbosePreference)"; $VerbosePreference = "SilentlyContinue";
                Import-Module -Name "SkypeForBusiness" -Global:$true -Force:$true -Verbose:$false -Debug:$false;
                $VerbosePreference = "$($strVerbosePreference)";
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strVerbosePreference";
            }
            
            ##### Generate The On Premises SIP Domains Array
            [System.Array]$arrSipDomains = @();
            If (-not -not (Get-Module -Name "SkypeForBusiness" -Verbose:$false -Debug:$false))
            {
                ##### Generate The On Premises SIP Domains Array
                Write-Debug "Generate The On Premises SIP Domains Array";
                [System.Array]$arrSipDomains = Get-CsSipDomain -Verbose:$false -Debug:$false;
            }
            
            Return $arrSipDomains;
        }
        
        #==================================================================================
        #endregion Verify If The SkypeForBusiness Module Is Available And Loaded
        #==================================================================================
        
        #==================================================================================
        #region Generate The Session Target
        #==================================================================================
        
        ##### Verify Whether Registrar Is Provided
        Write-Debug "Verify Whether Registrar Is Provided";
        If (-not "$($Registrar)")
        {
            ##### Retrieve Skype Registrar Pools From AD
            Write-Debug "Retrieve Skype Registrar Pools From AD";
            [System.Array]$arrFrontEndPools = @();
            [System.Array]$arrFrontEndPools = Search-SnsAdObject -DomainController "$(([ADSI]""LDAP://RootDSE"").dnsHostName)" `
                -LdapQuery "(&(objectClass=msRTCSIP-Pool)(objectCategory=CN=ms-RTC-SIP-Pool,$(([ADSI]""LDAP://RootDSE"").schemaNamingContext))(msRTCSIP-PoolData=ExtendedType=CentralRegistrar))" `
                -SearchRoot "$(([ADSI]""LDAP://RootDSE"").configurationNamingContext)" -ReturnProperties @("dNSHostName") -Verbose:$false -Debug:$false;
            #####
            
            ##### Ping All FrontEnd Pools And Take The Nearest One
            Write-Debug "Ping All FrontEnd Pools And Take The Nearest One";
            [System.String]$Registrar = $arrFrontEndPools | Select-Object -Property @{ "n" = "Fqdn"; "e" = {"$($_.Properties.dnshostname)"}} -Verbose:$false -Debug:$false | `
                Select-Object -Verbose:$false -Debug:$false `
                -Property  @( "Fqdn", @{ "n" = "TTL"; "e" = {[System.Int32]"$($objPing.Send(""$($_.Fqdn)"", 100, [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;
            #####
        }
        
        ##### Verify The Target Server Generation
        Write-Debug "Verify The Target Server Generation";
        If (-not "$($Registrar)")
        {
            [System.String]$strEventMessage = "There Is No Destination Registrar 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 Generate 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 Skype 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 Establish SfB On Premises Session
        #==================================================================================
        
        ##### Remove Any Previous Sessions
        Write-Debug "Remove Any Previous Sessions";
        Remove-SnsPreviousSessionsHelper -Filter "$($Registrar)" -ScrBlock $scrBlock -Verbose:$bolVerbose -Debug:$false;
        
        ##### Verify The Prerequisites For Remote PSSession
        Write-Debug "Verify The Prerequisites For Remote PSSession";
        If ((-not -not "$($Registrar)") -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)://$($Registrar)/OcsPowershell/" `
                -Prefix "$($Prefix)" -ProxyAccessType "NoProxyServer" -Attempts $Attempts -EventSource "$($EventSource)" -Verbose:$bolVerbose -Debug:$false;
            $VerbosePreference = "$($strInitialVerboseSettings)";
        }
        
        #==================================================================================
        #endregion Establish SfB On Premises Session
        #==================================================================================
        
        #==================================================================================
        #region Retrieve The SIP Domains Via The Remote Session
        #==================================================================================
        
        ##### Verify The PSSession Import
        If (-not -not "$($objSesModule.Name)")
        {
            ##### Verify Whether Get-CsSipDomain Command Is Among The Exported Commands
            Write-Debug "Verify Whether Get-CsSipDomain Command Is Among The Exported Commands";
            If ($objSesModule.ExportedCommands.Keys -icontains "Get-$($Prefix)CsSipDomain")
            {
                ##### Generate The On Premises SIP Domain Array
                Write-Debug "Generate The On Premises SIP Domain Array";
                [System.Array]$arrSipDomains = @();
                [System.Array]$arrSipDomains = Invoke-Command -ScriptBlock $scrBlock -Verbose:$false -Debug:$false;
                
                ##### Verify The SfB On Premises Session Import
                Write-Debug "Verify The SfB On Premises Session Import";
                If (($arrSipDomains.Count) -eq 0)
                {
                    [System.String]$strEventMessage = "Failed To Retrieve The On Premises CsSipDomain";
                    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 SIP 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 "arrFrontEndPools";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "scrBlock";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objPing";
        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 ($arrSipDomains.Count -gt 0))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $arrSipDomains;
        }
    }
}

##### Connect-SnsMicrosoftTeams ===================================================
Function Connect-SnsMicrosoftTeams ()
{
<#
.SYNOPSIS
Establishes Remote PowerShell Session To Microsoft Teams.
.DESCRIPTION
Establishes Remote PowerShell Session To Microsoft Teams.
NOTE: The CmdLet Requires MicrosoftTeams PowerShell Module To Be Preinstalled. https://bit.ly/3PFWTdL
WARNING: If Basic Authentication Is Not Enabled, Legacy *-Cs CmdLets Will Not Function Properly. For Remote
PowerShell, Basic Authentication Is Necessary. https://bit.ly/3PLGSmR
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 Teams:
-- 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 UserName
Specifies The UserName.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER ApplicationId
Specifies The ApplicationId Of The AppRegistration Used For MicrosoftGraph Connection.
Parameter Set: FolderPath
Parameter Alias: ClientID
Parameter Validation: Yes Using RegEx Pattern Matching Validation
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential Files Resides.
Whenever The Connection Is Established Using Both MicrosoftTeams And MicrosoftGraph The Folder Must Contain
The Credential Files For The UserPassword And The AppSecret.
The CmdLet Will Try To Enumerate The Username And The ClientID From The Credential Files.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: Yes Using Folder Existence Validation
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The User Credential File.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: Yes Using File Existence Validation
.PARAMETER ApplicationPasswordFilePath
Specifies The Full Absolute UNC Path To The Application Secret File.
Parameter Set: FilePath
Parameter Alias: ClientPasswordFilePath
Parameter Validation: Yes Using File Existence Validation
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object For The User.
Parameter Set: Credential
Parameter Alias: N/A
Parameter Validation: Yes Using Value Existence Validation
.PARAMETER ApplicationCredential
Specifies [System.Management.Automation.PSCredential] Object For The AppRegistration Secret.
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 TenantID
Specifies The TenantID GUID.
Whenever The Connection Is Established Using Both MicrosoftTeams And MicrosoftGraph Becomes Mandatory.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: Yes Using RegEx Pattern Matching 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 Teams SIP 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]$arrSipDomains = Connect-SnsMicrosoftTeams -UserName "john.smith@contoso.com" `
-FolderPath "C:\" -PassThru;
.EXAMPLE
Connect-SnsMicrosoftTeams -UserName "john.smith@contoso.com" -FolderPath "C:\" `
-ApplicationId "00000000-0000-0000-0000-000000000000" -TenantID "00000000-0000-0000-0000-000000000000";
.EXAMPLE
Connect-SnsMicrosoftTeams -FilePath "C:\john.smith@contoso.com.ini";
.EXAMPLE
Connect-SnsMicrosoftTeams -FilePath "C:\john.smith@contoso.com.ini" `
-ApplicationPasswordFilePath "C:\00000000-0000-0000-0000-000000000000.ini" `
-TenantID "00000000-0000-0000-0000-000000000000";
.EXAMPLE
Connect-SnsMicrosoftTeams -Credential $objCredential;
.EXAMPLE
Connect-SnsMicrosoftTeams -Credential $objCredential -ApplicationCredential $ApplicationCredential `
-TenantID "00000000-0000-0000-0000-000000000000";
.EXAMPLE
Connect-SnsMicrosoftTeams -Interactive;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[Alias("New-SfbOnlineSession", "Connect-SnsSkypeOnline")]
[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 = $false, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ClientID")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$ApplicationId,
    
    [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 = $false, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ClientPasswordFilePath")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$ApplicationPasswordFilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$ApplicationCredential,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")})]
    [ValidateNotNullOrEmpty()][System.String]$TenantID,
    
    [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-SnsMicrosoftTeams";
        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.Management.Automation.PSCredential]$objCredential = $null;
        [System.Management.Automation.PSCredential]$objAppCredential = $null;
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        [System.Object]$objVerif = $null;
        [System.Int32]$intI = 0;
        [System.Array]$arrSipDomains = @();
        
        #==================================================================================
        #region Initialize The Credentials Object
        #==================================================================================
        
        ##### Load MicrosoftTeams PowerShell Module
        Write-Debug "Load MicrosoftTeams PowerShell Module";
        If (Import-SnsModuleHelper -Module "MicrosoftTeams" -EventSource "$($EventSource)") { Return; }
        
        ###### Verify The ParameterSetName
        Write-Debug "Verify The ParameterSetName";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Management.Automation.PSCredential]$objAppCredential = $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 -not "$($ApplicationPasswordFilePath)")
                {
                    Write-Verbose "Import The Application Secret From A File";
                    [System.Management.Automation.PSCredential]$objAppCredential = Import-SnsCredentialFile -FilePath "$($ApplicationPasswordFilePath)" -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;
                
                If (-not -not "$($ApplicationId)")
                {
                    Write-Verbose "Import The Application Secret From A File";
                    [System.Management.Automation.PSCredential]$objAppCredential = Import-SnsCredentialFile -UserName "$($ApplicationId)" -FolderPath "$($FolderPath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                }
                
                Break;
            }
            
            "Credential"
            {
                Write-Verbose "Assign The Provided Credential Object";
                [System.Management.Automation.PSCredential]$objCredential = $Credential;
                
                If (-not -not "$($ApplicationCredential.UserName)")
                {
                    Write-Verbose "Assign The Provided Application Secret Credential Object";
                    [System.Management.Automation.PSCredential]$objAppCredential = $ApplicationCredential
                }
                
                Break;
            }
        }
        
        ##### Verify The Credential Object
        Write-Debug "Verify The Credential Object";
        If ((-not "$($objCredential.UserName)") -and (-not [System.Environment]::UserInteractive))
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.Management.Automation.PSCredential]$objAppCredential = $null;
            [System.String]$strEventMessage = "Failed To Enumerate The Credential For Teams";
            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;
        }
        
        ##### Verify The TenantID Object
        Write-Debug "Verify The TenantID Object";
        If ((-not -not "$($objAppCredential.UserName)") -and (-not "$($TenantID)"))
        {
            [System.Management.Automation.PSCredential]$objCredential = $null;
            [System.Management.Automation.PSCredential]$objAppCredential = $null;
            [System.String]$strEventMessage = "TenantID Is Required When AccessTokens Authentication Is Used";
            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 MicrosoftTeams Connection
        #==================================================================================
        
        ##### Initialize The Splatting HashTable
        Write-Debug "Initialize The Splatting HashTable";
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        $hshSplat.Add("WhatIf", $false);
        $hshSplat.Add("Confirm", $false);
        $hshSplat.Add("Verbose", $false);
        $hshSplat.Add("Debug", $false);
        If ((-not -not "$($TenantID)") -and (-not "$($objAppCredential.UserName)")) { $hshSplat.Add("TenantID", "$($TenantID)"); }
        
        ##### Verify Whether We Have User Credential Object
        Write-Debug "Verify Whether We Have User Credential Object";
        If (-not -not "$($objCredential.UserName)")
        {
            ##### Verify Whether We Have App Credential Object
            Write-Debug "Verify Whether We Have App Credential Object";
            If (-not -not "$($objAppCredential.UserName)")
            {
                #==================================================================================
                #region Authenticate Against MicrosoftGraph And MicrosoftTeams
                #==================================================================================
                
                ##### Initialize The Tokens Splatting HashTable
                Write-Debug "Initialize The Tokens Splatting HashTable";
                [System.Collections.Specialized.OrderedDictionary]$hshTokenSplat = [Ordered]@{};
                $hshTokenSplat.Add("URI", "https://login.microsoftonline.com/$($TenantID)/oauth2/v2.0/token");
                $hshTokenSplat.Add("Method", "POST");
                $hshTokenSplat.Add("ContentType", "application/x-www-form-urlencoded");
                $hshTokenSplat.Add("Verbose", $false);
                $hshTokenSplat.Add("Debug", $false);
                
                ##### Initialize The Rest Request Body
                Write-Debug "Initialize The Rest Request Body";
                [System.String]$strBody = "";
                [System.String]$strBody = "client_id=$($objAppCredential.UserName)&client_secret=$( `
                    [Net.WebUtility]::URLEncode(""$($objAppCredential.GetNetworkCredential().Password)"") `
                    )&grant_type=password&username=$($objCredential.UserName)&password=$( `
                    [Net.WebUtility]::URLEncode(""$($objCredential.GetNetworkCredential().Password)""))"
;
                #####
                
                ##### Loop The MicrosoftGraph Authentication
                Write-Debug "Loop The MicrosoftGraph Authentication";
                [System.Int32]$intI = 0;
                [System.String]$strGraphToken = "";
                While ((-not "$($strGraphToken)") -and ($intI -lt $Attempts))
                {
                    ##### Authenticate Against MicrosoftGraph
                    Write-Verbose "Authenticate Against MicrosoftGraph";
                    [System.String]$strGraphToken = "";
                    [System.String]$strGraphToken = "$((Invoke-RestMethod @hshTokenSplat -Body ""$($strBody)&scope=https://graph.microsoft.com/.default"").access_token)";
                    Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
                }
                
                ##### Verify The MicrosoftGraph Authentication
                Write-Debug "Verify The MicrosoftGraph Authentication";
                If (-not "$($strGraphToken)")
                {
                    [System.String]$strEventMessage = "Failed To Authenticate Against MicrosoftGraph";
                    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;
                }
                
                ##### Loop The MicrosoftTeams Authentication
                Write-Debug "Loop The MicrosoftTeams Authentication";
                [System.Int32]$intI = 0;
                [System.String]$strTeamsToken = "";
                While ((-not "$($strTeamsToken)") -and ($intI -lt $Attempts))
                {
                    ##### Authenticate Against MicrosoftTeams
                    Write-Verbose "Authenticate Against MicrosoftTeams";
                    [System.String]$strTeamsToken = "";
                    [System.String]$strTeamsToken = "$((Invoke-RestMethod @hshTokenSplat -Body ""$($strBody)&scope=48ac35b8-9aa8-4d74-927d-1f4a14a0b239/.default"").access_token)";
                    Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
                }
                
                ##### Verify The MicrosoftTeams Authentication
                Write-Debug "Verify The MicrosoftTeams Authentication";
                If (-not "$($strTeamsToken)")
                {
                    [System.String]$strEventMessage = "Failed To Authenticate Against MicrosoftTeams";
                    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;
                }
                
                ##### Add The AccessTokens To Connect-MicrosoftTeams Splatting HashTable
                Write-Debug "Add The AccessTokens To Connect-MicrosoftTeams Splatting HashTable";
                $hshSplat.Add("AccessTokens", @("$($strGraphToken)", "$($strTeamsToken)"));
                [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 1;
                
                ##### Reset The Variables
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strTeamsToken";
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strGraphToken";
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strBody";
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshTokenSplat";
                
                #==================================================================================
                #endregion Authenticate Against MicrosoftGraph And MicrosoftTeams
                #==================================================================================
            } Else
            {
                $hshSplat.Add("Credential", $objCredential);
            }
        }
        
        ##### Loop The Session Initialization
        Write-Debug "Loop The Session Initialization";
        [System.Int32]$intI = 0;
        [Microsoft.TeamsCmdlets.Powershell.Connect.Models.PSAzureContext]$objVerif = $null;
        While ((-not "$($objVerif.TenantId.Guid)") -and ($intI -lt $Attempts))
        {
            ##### Connecting To MicrosoftTeams
            Write-Verbose "Connecting To MicrosoftTeams";
            [Microsoft.TeamsCmdlets.Powershell.Connect.Models.PSAzureContext]$objVerif = $null;
            [Microsoft.TeamsCmdlets.Powershell.Connect.Models.PSAzureContext]$objVerif = Connect-MicrosoftTeams @hshSplat;
            
            ##### Process The Loop Variable and TimeOut
            Start-Sleep -Seconds $(2 * $intI) -Verbose:$false -Debug:$false; [System.Int32]$intI = $intI + 1;
        }
        
        ##### Verify The MicrosoftTeams Session Creation
        Write-Debug "Verify The MicrosoftTeams Session Creation";
        If (-not "$($objVerif.TenantId.Guid)")
        {
            [System.String]$strEventMessage = "Failed To Establish MicrosoftTeams Connection";
            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 Establish MicrosoftTeams Connection
        #==================================================================================
        
        #==================================================================================
        #region Retrieve The SIP Domains Via The Remote Session
        #==================================================================================
        
        ##### Verify The PSSession Import
        Write-Debug "Verify The PSSession Import";
        If (-not -not "$($objVerif.TenantId.Guid)")
        {
            ##### Generate The SfB Online SIP Domain Array
            Write-Debug "Generate The SfB Online SIP Domain Array";
            [System.Array]$arrSipDomains = @();
            [System.Array]$arrSipDomains = Get-CsOnlineSipDomain -Verbose:$false -Debug:$false;
            
            ##### Verify The SfB Online Session Import
            Write-Debug "Verify The SfB Online Session Import";
            If (($arrSipDomains.Count) -eq 0)
            {
                [System.String]$strEventMessage = "Failed To Retrieve The CsOnlineSipDomain";
                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 Retrieve The SIP Domains Via The Remote Session
        #==================================================================================
        
        ##### 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 "objVerif";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplat";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objAppCredential";
        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 ($arrSipDomains.Count -gt 0))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            Return $arrSipDomains;
        }
    }
}

##### Disable-SnsMfa ==============================================================
Function Disable-SnsMfa ()
{
<#
.SYNOPSIS
Disables Per User MultiFactor Authentication For A Specified Azure Account.
.DESCRIPTION
Disables Per User MultiFactor Authentication For A Specified Azure Account.
The CmdLet Disables The Per User MFA Gracefully, In Terms That Any Set By The User StrongAuthenticationMethods Are
Preserved. Which Means That If The MFA Is Disabled Temporary After Re-Enablement The User Wont Be Required To Set
Up The MFA From Scratch. Which Means That The Authentication App And The Remaining Configuration Is Preserved.
Unfortunately The AppPasswords Cannot Be Preserved. They Can Be Used Only Whenever MFA Is Enabled And Are Lost
Immediately With The Disablement.
The CmdLet Accept As Input The AzureAD Account UserPrincipalName String, AzureAD Account ObjectId String Or
AzureAD MSOL User Object. On Input Are Evaluated The TypeName Of The Provided Objects. Therefore The CmdLet Will
Accept Input From Pipeline Or Collection Variable Of All The Specified TypeName Simultaneously.
Using WhatIf Switch Parameter Allows The CmdLet To Be Used For MFA Report Generation Without Actually Modification
Of Users MFA Status.
.PARAMETER InputObject
Specifies Either MsolUser Object, Or UserPrincipalName, Or AzureAD ObjectId Of The User Or Users Which Have To
Be MFA Enabled.
Parameter Alias: "UserPrincipalName", "ObjectId"
Parameter Validation: Yes Using Object TypeName And RegEx Matching Validation
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Enable The MFA.
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 -InputObject Which Accept Values Of Type [System.Array] And [System.String[]]
.OUTPUTS
Pipeline Output [SnsPsModule.SnsMfaStatus[]] Which Contains A Report About Users MFA Status
.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
[SnsPsModule.SnsMfaStatus[]]$arrMfa = Disable-SnsMfa -InputObject $arrCollection;
.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 = "High")]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias("UserPrincipalName", "ObjectId")]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({( `
        ("$(($_ | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -like "*Microsoft.Online.Administration.User") `
        -or `
        ("$($_)" -match "$([SnsPsModule.SnsPatterns]::UPNPattern)") `
        -or `
        ("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)") `
    )})]
    [System.Array]$InputObject,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 3
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Disable-SnsMfa";
        #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"; };
        
        #==================================================================================
        #region Verify The Msol V1 Service Connection
        #==================================================================================
        
        ##### Verify The Msol V1 Service Connection
        Write-Verbose "Verify The Msol V1 Service Connection";
        If (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object {"$($_.Name)" -like "ArrMsolLicenses"} -Verbose:$false -Debug:$false))
        {
            Write-Error "Please Establish A Connection To Msol Service" -ErrorAction "Stop";
        }
        
        #==================================================================================
        #endregion Verify The Msol V1 Service Connection
        #==================================================================================
        
        ##### Initialize The Variables
        [System.Boolean]$bolInteractive = [System.Environment]::UserInteractive;
        [System.Boolean]$bolProgrBar = $false;
        [System.UInt32]$intI = 0;
        [SnsPsModule.SnsMfaStatus]$objMfa = $null;
        [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
        [Microsoft.Online.Administration.StrongAuthenticationMethod[]]$arrAuthMethods = $null;
        [System.Int32]$intIn = 0;
        [System.Boolean]$bolMethOk = $false;
    }
    
    ##### Override The Process Method
    Process
    {
        Write-Debug "Override Process Method";
        Write-Verbose "";
        
        ##### Verify Whether There Are InputObjects
        Write-Debug "Verify Whether There Are InputObjects";
        If ($InputObject.Count -gt 0)
        {
            ##### Evaluate Whether ProgressBar Is Required
            [System.Boolean]$bolProgrBar = $bolInteractive -and ($InputObject.Count -gt 5);
            
            ##### Process Each Input Object
            Write-Debug "Process Each Input Object";
            [System.UInt32]$intI = 0;
            For ([System.UInt32]$intI = 0; $intI -lt $InputObject.Count; $intI++)
            {
                ##### Evaluate The Number Of Input Objects
                If ($bolProgrBar)
                {
                    Write-Progress -Activity "Disable-SnsMfa" -Id 1 -PercentComplete (($intI * 100) / $InputObject.Count) -Verbose:$false -Debug:$false;
                }
                
                #==================================================================================
                #region Retrieve MsolUser Object From AzureAD
                #==================================================================================
                
                ##### Generate A Mfa Object
                Write-Debug "Generate A Mfa Object";
                [SnsPsModule.SnsMfaStatus]$objMfa = [SnsPsModule.SnsMfaStatus]::new();
                
                ##### Verify What Kind Of Input Was Provided
                Write-Debug "Verify What Kind Of Input Was Provided";
                If  ("$(($InputObject[$intI] | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -like "*Microsoft.Online.Administration.User")
                {
                    ##### Assign The Msol User Object To The Corresponding Object Property
                    $objMfa.MsolUser = $InputObject[$intI] | Select-Object -Property * -Verbose:$false -Debug:$false;
                    $objMfa.MsolUser | Add-Member -TypeName "Selected.Microsoft.Online.Administration.User";
                    
                    ##### Verify The Provided Object
                    Write-Debug "Verify The Provided Object";
                    If ( `
                        ("$($InputObject[$intI].ObjectId.Guid)" -notmatch "$([SnsPsModule.SnsPatterns]::GUIDPattern)") `
                        -or `
                        ("$($InputObject[$intI].UserPrincipalName)" -notmatch "$([SnsPsModule.SnsPatterns]::UPNPattern)") `
                    )
                    {
                        Write-Error "Unable To Recognize The Provided Input Object";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    }
                } ElseIf ("$($InputObject[$intI])" -match "$([SnsPsModule.SnsPatterns]::UPNPattern)")
                {
                    ##### Assign The UPN Property To The Corresponding Object Property
                    $objMfa.UserPrincipalName = "$($InputObject[$intI])";
                    
                    ##### Query The Msol V1 About User With UPN
                    Write-Debug "Query The Msol V1 About User With UPN: ""$($objMfa.UserPrincipalName)""";
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -UserPrincipalName "$($objMfa.UserPrincipalName)" -Verbose:$false -Debug:$false;
                    
                    If ($arrMsolUsr.Count -eq 1)
                    {
                        $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                    } ElseIf ($arrMsolUsr.Count -lt 1)
                    {
                        Write-Error "Unable To Find MsolUser With UPN: ""$($objMfa.UserPrincipalName)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    } ElseIf ($arrMsolUsr.Count -gt 1)
                    {
                        Write-Error "UPN Conflict in Msol V1 Service About ""$($objMfa.UserPrincipalName)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    }
                } ElseIf ("$($InputObject[$intI])" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")
                {
                    ##### Assign The UPN Property To The Corresponding Object Property
                    $objMfa.ObjectId = "$($InputObject[$intI])";
                    
                    ##### Query The Msol V1 About User With UPN
                    Write-Debug "Query The Msol V1 About User With ObjectId: ""$($objMfa.ObjectId)""";
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -ObjectId "$($objMfa.ObjectId)" -Verbose:$false -Debug:$false;
                    
                    If ($arrMsolUsr.Count -eq 1)
                    {
                        $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                    } ElseIf ($arrMsolUsr.Count -lt 1)
                    {
                        Write-Error "Unable To Find MsolUser With ObjectId: ""$($objMfa.ObjectId)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    } ElseIf ($arrMsolUsr.Count -gt 1)
                    {
                        Write-Error "ObjectId Conflict in Msol V1 Service About ""$($objMfa.ObjectId)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    }
                }
                
                #==================================================================================
                #endregion Retrieve MsolUser Object From AzureAD
                #==================================================================================
                
                #==================================================================================
                #region Calculate The Initial State
                #==================================================================================
                
                ##### Continue If The MsolUser Is Generated
                Write-Debug "Continue If The MsolUser Is Generated";
                If ("$(($objMfa.MsolUser | Get-Member -Verbose:$false -Debug:$false)[0].TypeName)" -eq "Selected.Microsoft.Online.Administration.User")
                {
                    ##### Evaluate The Current Object State
                    Write-Debug "Evaluate The Current Object State";
                    $objMfa.UserPrincipalName = "$($objMfa.MsolUser.UserPrincipalName)";
                    $objMfa.DisplayName = "$($objMfa.MsolUser.DisplayName)";
                    $objMfa.ObjectId = "$($objMfa.MsolUser.ObjectId.Guid)";
                    $objMfa.MfaState = "$($objMfa.MsolUser.StrongAuthenticationRequirements[0].State)";
                    $objMfa.ValueCorrect = (-not "$($objMfa.MfaState)");
                    Write-Verbose "Found UPN: ""$($objMfa.UserPrincipalName)"" ObjectId: ""$($objMfa.ObjectId)"" MfaState: ""$($objMfa.MfaState)""";
                    
                    ##### Get Users StrongAuthenticationMethods
                    [Microsoft.Online.Administration.StrongAuthenticationMethod[]]$arrAuthMethods = $objMfa.MsolUser.StrongAuthenticationMethods;
                }
                
                #==================================================================================
                #endregion Calculate The Initial State
                #==================================================================================
                
                #==================================================================================
                #region Disable-SnsMfa
                #==================================================================================
                
                ##### Continue If The MsolUser Is Retrieved Successfully
                Write-Debug "Continue If The MsolUser Is Retrieved Successfully"
                If ("$(($objMfa.MsolUser | Get-Member -Verbose:$false -Debug:$false)[0].TypeName)" -eq "Selected.Microsoft.Online.Administration.User")
                {
                    ##### Verify Whether The MFA Is Not Enabled
                    Write-Debug "Verify Whether The MFA Is Not Enabled";
                    If (-not $objMfa.ValueCorrect)
                    {
                        ##### Invoke ShouldProcess Method
                        Write-Debug "Invoke ShouldProcess Method";
                        If ($PSCmdlet.ShouldProcess("$($objMfa.UserPrincipalName)"))
                        {
                            #==================================================================================
                            #region Disable-MFA
                            #==================================================================================
                            
                            ##### Loop The MFA Disablement
                            Write-Debug "Loop The MFA Disablement";
                            [System.Int32]$intIn = 0;
                            While ((-not $objMfa.ValueCorrect) -and ($intIn -lt $Attempts))
                            {
                                ##### Disable MFA Of The User
                                Write-Verbose "Disable MFA Of: ""$($objMfa.UserPrincipalName)""";
                                Set-MsolUser -ObjectId "$($objMfa.ObjectId)" -StrongAuthenticationRequirements @() -Verbose:$false -Debug:$false | Out-Null;
                                
                                ##### Process The Loop Variables
                                Write-Debug "Process The Loop Variables";
                                [System.Int32]$intIn = $intIn + 1;
                                $objMfa.ValueModified = $true;
                                $objMfa.MsolUser = $null;
                                $objMfa.MfaState = "Enabled";
                                $objMfa.ValueCorrect = $false;
                                Start-Sleep -Seconds 1 -Verbose:$false -Debug:$false;
                                
                                ##### Query The Msol V1 About User With UPN
                                Write-Debug "Query The Msol V1 About User With UPN: ""$($objMfa.UserPrincipalName)""";
                                [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                                [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -ObjectId "$($objMfa.ObjectId)" -Verbose:$false -Debug:$false;
                                
                                ##### Verify The Msol V1 Query Output
                                Write-Debug "Verify The Msol V1 Query Output";
                                If ($arrMsolUsr.Count -eq 1)
                                {
                                    ##### Update The Object Properties
                                    Write-Debug "Update The Object Properties";
                                    $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                                    $objMfa.MfaState = "$($objMfa.MsolUser.StrongAuthenticationRequirements[0].State)";
                                    $objMfa.ValueCorrect = (-not "$($objMfa.MfaState)");
                                }
                            }
                            
                            #==================================================================================
                            #endregion Disable-MFA
                            #==================================================================================
                            
                            #==================================================================================
                            #region Put The StrongAuthenticationMethods Back
                            #==================================================================================
                            
                            ##### Check Whether There Are Methods To Be Reverted
                            Write-Debug "Check Whether There Are Methods To Be Reverted";
                            If ($arrAuthMethods.Count -gt 0)
                            {
                                ##### Verify The Current Authentication Methods
                                [System.Boolean]$bolMethOk = $false;
                                [System.Boolean]$bolMethOk = ($arrAuthMethods.Count -eq $objMfa.MsolUser.StrongAuthenticationMethods.Count);
                                
                                ##### Loop The MFA Methods Insert
                                Write-Debug "Loop The MFA Methods Insert";
                                [System.Int32]$intIn = 0;
                                While ((-not $bolMethOk) -and ($intIn -lt $Attempts))
                                {
                                    ##### Set StrongAuthenticationMethods Back To The User
                                    Write-Verbose "Set The Old StrongAuthenticationMethods Back To: ""$($objMfa.UserPrincipalName)""";
                                    Set-MsolUser -ObjectId "$($objMfa.ObjectId)" -StrongAuthenticationMethods $arrAuthMethods -Verbose:$false -Debug:$false | Out-Null;
                                    
                                    ##### Process The Loop Variables
                                    Write-Debug "Process The Loop Variables";
                                    [System.Int32]$intIn = $intIn + 1;
                                    $objMfa.MsolUser = $null;
                                    [System.Boolean]$bolMethOk = $false;
                                    Start-Sleep -Seconds 1 -Verbose:$false -Debug:$false;
                                    
                                    ##### Query The Msol V1 About User With UPN
                                    Write-Debug "Query The Msol V1 About User With UPN: ""$($objMfa.UserPrincipalName)""";
                                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -ObjectId "$($objMfa.ObjectId)" -Verbose:$false -Debug:$false;
                                    #####
                                    
                                    ##### Verify The Msol V1 Query Output
                                    Write-Debug "Verify The Msol V1 Query Output";
                                    If ($arrMsolUsr.Count -eq 1)
                                    {
                                        ##### Update The Object Properties
                                        Write-Debug "Update The Object Properties";
                                        $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                                        [System.Boolean]$bolMethOk = ($arrAuthMethods.Count -eq $objMfa.MsolUser.StrongAuthenticationMethods.Count);
                                    }
                                }
                                
                                ##### Verify Whether The Methods Are Set Back or The Number Of Attempts Exceeded
                                If (-not $bolMethOk) { $objMfa.ValueCorrect = $false; };
                            }
                            
                            #==================================================================================
                            #endregion Put The StrongAuthenticationMethods Back
                            #==================================================================================
                        }
                    }
                    Else
                    {
                        Write-Verbose "The MFA Of User: ""$($objMfa.UserPrincipalName)"" Is Already Disabled";
                    }
                }
                
                #==================================================================================
                #endregion Disable-SnsMfa
                #==================================================================================
                
                ##### Pass The Output Object To The Pipeline
                Write-Debug "Pass Output Object To The Pipeline";
                $PSCmdlet.WriteObject($objMfa);
            }
            
            If ($bolProgrBar)
            {
                Write-Progress -Activity "Disable-SnsMfa" -Id 1 -PercentComplete 100 -Verbose:$false -Debug:$false;
                Write-Progress -Activity "Disable-SnsMfa" -Id 1 -Completed -Verbose:$false -Debug:$false;
            }
        }
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolMethOk";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intIn";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrAuthMethods";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrMsolUsr";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objMfa";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolProgrBar";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolInteractive";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
    }
}

##### Enable-SnsMfa ===============================================================
Function Enable-SnsMfa ()
{
<#
.SYNOPSIS
Enables Per User MultiFactor Authentication For A Specified Azure Account.
.DESCRIPTION
Enables Per User MultiFactor Authentication For A Specified Azure Account.
The CmdLet Accepts As Input The AzureAD Account UserPrincipalName String, AzureAD Account ObjectId String Or
AzureAD MSOL User Object. On Input Are Evaluated The TypeName Of The Provided Objects. Therefore The CmdLet Will
Accept Input From Pipeline Or Collection Variable Of All The Specified TypeName Simultaneously.
Using WhatIf Switch Parameter Allows The CmdLet To Be Used For MFA Report Generation Without Actually Modification
Of Users MFA Status.
.PARAMETER InputObject
Specifies Either MsolUser Object, Or UserPrincipalName, Or AzureAD ObjectId Of The User Or Users Which Have To
Be MFA Enabled.
Parameter Alias: "UserPrincipalName", "ObjectId"
Parameter Validation: Yes Using Object TypeName And RegEx Matching Validation
.PARAMETER AuthRequirement
Specifies A Microsoft.Online.Administration.StrongAuthenticationRequirement Object With The Required MFA
Parameters.
If Omitted The CmdLet Will Generate One Internally With Default Parameters:
-- MFA Status "Enabled"
-- Keep Any Existing Authentication Methods And Devices.
Parameter Alias: N/A
Parameter Validation: Yes, Using Object TypeName Validation
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Enable The MFA.
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER Force
Specifies To The CmdLet That Exact Matching Of Enforce And Enabled Have To Be Used.
If Omitted The CmdLet Will Consider Users With Enabled MFA To Be Compliant When AuthRequirement Object Require Enforced MFA And Vice Versa.
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 -InputObject Which Accept Values Of Type [System.Array] And [System.String[]]
.OUTPUTS
Pipeline Output [SnsPsModule.SnsMfaStatus[]] Which Contains A Report About Users MFA Status
.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
[SnsPsModule.SnsMfaStatus[]]$arrMfa = Enable-SnsMfa -InputObject $arrCollection `
-AuthRequirement $objAuthRequirement -Force;
.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 = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias("UserPrincipalName", "ObjectId")]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({( `
        ("$(($_ | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -like "*Microsoft.Online.Administration.User") `
        -or `
        ("$($_)" -match "$([SnsPsModule.SnsPatterns]::UPNPattern)") `
        -or `
        ("$($_)" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)") `
    )})]
    [System.Array]$InputObject,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({"$(($_ | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -eq "Microsoft.Online.Administration.StrongAuthenticationRequirement"})]
    [System.Object]$AuthRequirement,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Int32]$Attempts = 3,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Force = $false
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Enable-SnsMfa";
        #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"; };
        
        #==================================================================================
        #region Verify The Msol V1 Service Connection
        #==================================================================================
        
        ##### Verify The Msol V1 Service Connection
        Write-Verbose "Verify The Msol V1 Service Connection";
        If (-not (Get-Variable -Verbose:$false -Debug:$false | Where-Object {"$($_.Name)" -like "ArrMsolLicenses"} -Verbose:$false -Debug:$false))
        {
            Write-Error "Please Establish A Connection To Msol Service" -ErrorAction "Stop";
        }
        
        #==================================================================================
        #endregion Verify The Msol V1 Service Connection
        #==================================================================================
        
        ##### Initialize The Variables
        [Microsoft.Online.Administration.StrongAuthenticationRequirement]$objAuthRequirement = $null;
        [System.Boolean]$bolInteractive = [System.Environment]::UserInteractive;
        [System.Boolean]$bolProgrBar = $false;
        [System.UInt32]$intI = 0;
        [SnsPsModule.SnsMfaStatus]$objMfa = $null;
        [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
        [System.Int32]$intIn = 0;
        
        #==================================================================================
        #region Generate The StrongAuthenticationRequirement Object
        #==================================================================================
        
        ##### Verify Whether StrongAuthenticationRequirement Object Is Provided
        Write-Debug "Verify Whether StrongAuthenticationRequirement Object Is Provided";
        If (-not "$($AuthRequirement.State)")
        {
            ##### Generate The StrongAuthenticationRequirement Object
            Write-Verbose "Generate The StrongAuthenticationRequirement Object";
            [Microsoft.Online.Administration.StrongAuthenticationRequirement]$objAuthRequirement = $null;
            [Microsoft.Online.Administration.StrongAuthenticationRequirement]$objAuthRequirement = New-Object -TypeName 'Microsoft.Online.Administration.StrongAuthenticationRequirement';
            $objAuthRequirement.RelyingParty = "*";
            #$objAuthRequirement.State = 'Enforced';
            $objAuthRequirement.State = 'Enabled';
            $objAuthRequirement.RememberDevicesNotIssuedBefore = [System.DateTime]::Now;
        }
        Else
        {
            [Microsoft.Online.Administration.StrongAuthenticationRequirement]$objAuthRequirement = $null;
            [Microsoft.Online.Administration.StrongAuthenticationRequirement]$objAuthRequirement = $AuthRequirement
        }
        
        #==================================================================================
        #endregion Generate The StrongAuthenticationRequirement Object
        #==================================================================================
    }
    
    ##### Override The Process Method
    Process
    {
        Write-Debug "Override Process Method";
        Write-Verbose "";
        
        ##### Verify Whether There Are InputObjects
        Write-Debug "Verify Whether There Are InputObjects";
        If ($InputObject.Count -gt 0)
        {
            ##### Evaluate Whether ProgressBar Is Required
            [System.Boolean]$bolProgrBar = $bolInteractive -and ($InputObject.Count -gt 5);
            
            ##### Process Each Input Object
            Write-Debug "Process Each Input Object";
            [System.UInt32]$intI = 0;
            For ([System.UInt32]$intI = 0; $intI -lt $InputObject.Count; $intI++)
            {
                If ($bolProgrBar)
                {
                    Write-Progress -Activity "Enable-SnsMfa" -Id 1 -PercentComplete (($intI * 100) / $InputObject.Count) -Verbose:$false -Debug:$false;
                }
                
                #==================================================================================
                #region Retrieve MsolUser Object From AzureAD
                #==================================================================================
                
                ##### Generate A Mfa Object
                Write-Debug "Generate A Mfa Object";
                [SnsPsModule.SnsMfaStatus]$objMfa = [SnsPsModule.SnsMfaStatus]::new();
                
                ##### Verify What Kind Of Input Was Provided
                Write-Debug "Verify What Kind Of Input Was Provided";
                If  ("$(($InputObject[$intI] | Get-Member -Verbose:$false -Debug:$false )[0].TypeName)" -like "*Microsoft.Online.Administration.User")
                {
                    ##### Assign The Msol User Object To The Corresponding Object Property
                    $objMfa.MsolUser = $InputObject[$intI] | Select-Object -Property * -Verbose:$false -Debug:$false;
                    $objMfa.MsolUser | Add-Member -TypeName "Selected.Microsoft.Online.Administration.User";
                    
                    ##### Verify The Provided Object
                    Write-Debug "Verify The Provided Object";
                    If ( `
                        ("$($InputObject[$intI].ObjectId.Guid)" -notmatch "$([SnsPsModule.SnsPatterns]::GUIDPattern)") `
                        -or `
                        ("$($InputObject[$intI].UserPrincipalName)" -notmatch "$([SnsPsModule.SnsPatterns]::UPNPattern)") `
                    )
                    {
                        Write-Error "Unable To Recognize The Provided Input Object";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    }
                } ElseIf ("$($InputObject[$intI])" -match "$([SnsPsModule.SnsPatterns]::UPNPattern)")
                {
                    ##### Assign The UPN Property To The Corresponding Object Property
                    $objMfa.UserPrincipalName = "$($InputObject[$intI])";
                    
                    ##### Query The Msol V1 About User With UPN
                    Write-Debug "Query The Msol V1 About User With UPN: ""$($objMfa.UserPrincipalName)""";
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -UserPrincipalName "$($objMfa.UserPrincipalName)" -Verbose:$false -Debug:$false;
                    
                    If ($arrMsolUsr.Count -eq 1)
                    {
                        $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                    } ElseIf ($arrMsolUsr.Count -lt 1)
                    {
                        Write-Error "Unable To Find MsolUser With UPN: ""$($objMfa.UserPrincipalName)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    } ElseIf ($arrMsolUsr.Count -gt 1)
                    {
                        Write-Error "UPN Conflict in Msol V1 Service About ""$($objMfa.UserPrincipalName)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    }
                } ElseIf ("$($InputObject[$intI])" -match "$([SnsPsModule.SnsPatterns]::GUIDPattern)")
                {
                    ##### Assign The ObjectId Property To The Corresponding Object Property
                    $objMfa.ObjectId = "$($InputObject[$intI])";
                    
                    ##### Query The Msol V1 About User With UPN
                    Write-Debug "Query The Msol V1 About User With ObjectId: ""$($objMfa.ObjectId)""";
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                    [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -ObjectId "$($objMfa.ObjectId)" -Verbose:$false -Debug:$false;
                    
                    If ($arrMsolUsr.Count -eq 1)
                    {
                        $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                    } ElseIf ($arrMsolUsr.Count -lt 1)
                    {
                        Write-Error "Unable To Find MsolUser With ObjectId: ""$($objMfa.ObjectId)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    } ElseIf ($arrMsolUsr.Count -gt 1)
                    {
                        Write-Error "ObjectId Conflict in Msol V1 Service About ""$($objMfa.ObjectId)""";
                        $PSCmdlet.WriteObject($objMfa);
                        Continue;
                    }
                }
                
                #==================================================================================
                #endregion Retrieve MsolUser Object From AzureAD
                #==================================================================================
                
                #==================================================================================
                #region Calculate The Initial State
                #==================================================================================
                
                ##### Continue If The MsolUser Is Generated
                Write-Debug "Continue If The MsolUser Is Generated";
                If ("$(($objMfa.MsolUser | Get-Member -Verbose:$false -Debug:$false)[0].TypeName)" -eq "Selected.Microsoft.Online.Administration.User")
                {
                    ##### Evaluate The Current Object State
                    Write-Debug "Evaluate The Current Object State";
                    $objMfa.UserPrincipalName = "$($objMfa.MsolUser.UserPrincipalName)";
                    $objMfa.DisplayName = "$($objMfa.MsolUser.DisplayName)";
                    $objMfa.ObjectId = "$($objMfa.MsolUser.ObjectId.Guid)";
                    $objMfa.MfaState = "$($objMfa.MsolUser.StrongAuthenticationRequirements[0].State)";
                    $objMfa.ValueCorrect = ( `
                        ((-not $Force.IsPresent) -and (-not -not "$($objMfa.MfaState)")) `
                        -or `
                        (($Force.IsPresent) -and ("$($objMfa.MfaState)" -eq "$($objAuthRequirement.State)")) `
                    );
                    Write-Verbose "Found UPN: ""$($objMfa.UserPrincipalName)"" ObjectId: ""$($objMfa.ObjectId)"" MfaState: ""$($objMfa.MfaState)""";
                }
                
                #==================================================================================
                #endregion Calculate The Initial State
                #==================================================================================
                
                #==================================================================================
                #region Enable-SnsMfa
                #==================================================================================
                
                ##### Continue If The MsolUser Is Retrieved Successfully
                Write-Debug "Continue If The MsolUser Is Retrieved Successfully"
                If ("$(($objMfa.MsolUser | Get-Member -Verbose:$false -Debug:$false)[0].TypeName)" -eq "Selected.Microsoft.Online.Administration.User")
                {
                    ##### Verify Whether The MFA Is Not Enabled
                    Write-Debug "Verify Whether The MFA Is Not Enabled";
                    If (-not $objMfa.ValueCorrect)
                    {
                        ##### Invoke ShouldProcess Method
                        Write-Debug "Invoke ShouldProcess Method";
                        If ($PSCmdlet.ShouldProcess("$($objMfa.UserPrincipalName)"))
                        {
                            ##### Loop The MFA Enforcement
                            Write-Debug "Loop The MFA Enforcement";
                            [System.Int32]$intIn = 0;
                            While ((-not $objMfa.ValueCorrect) -and ($intIn -lt $Attempts))
                            {
                                ##### Enforce MFA To The User
                                Write-Verbose "$($objAuthRequirement.State) MFA To: ""$($objMfa.UserPrincipalName)""";
                                Set-MsolUser -ObjectId "$($objMfa.ObjectId)" -StrongAuthenticationRequirements $objAuthRequirement -Verbose:$false -Debug:$false | Out-Null;
                                
                                ##### Process The Loop Variables
                                Write-Debug "Process The Loop Variables";
                                [System.Int32]$intIn = $intIn + 1;
                                $objMfa.ValueModified = $true;
                                $objMfa.MsolUser = $null;
                                $objMfa.MfaState = "";
                                $objMfa.ValueCorrect = $false;
                                Start-Sleep -Seconds 1 -Verbose:$false -Debug:$false;
                                
                                ##### Query The Msol V1 About User With UPN
                                Write-Debug "Query The Msol V1 About User With UPN: ""$($objMfa.UserPrincipalName)""";
                                [Microsoft.Online.Administration.User[]]$arrMsolUsr = @();
                                [Microsoft.Online.Administration.User[]]$arrMsolUsr = Get-MsolUser -ObjectId "$($objMfa.ObjectId)" -Verbose:$false -Debug:$false;
                                
                                ##### Verify The Msol V1 Query Output
                                Write-Debug "Verify The Msol V1 Query Output";
                                If ($arrMsolUsr.Count -eq 1)
                                {
                                    ##### Update The Object Properties
                                    Write-Debug "Update The Object Properties";
                                    $objMfa.MsolUser = $arrMsolUsr[0] | Select-Object -Property * -Verbose:$false -Debug:$false;
                                    $objMfa.MfaState = "$($objMfa.MsolUser.StrongAuthenticationRequirements[0].State)";
                                    $objMfa.ValueCorrect = ( `
                                        ((-not $Force.IsPresent) -and (-not -not "$($objMfa.MfaState)")) `
                                        -or `
                                        (($Force.IsPresent) -and ("$($objMfa.MfaState)" -eq "$($objAuthRequirement.State)")) `
                                    );
                                }
                            }
                        }
                    }
                    Else
                    {
                        Write-Verbose "The MFA Of User: ""$($objMfa.UserPrincipalName)"" Is Already: ""$($objMfa.MfaState)""";
                    }
                }
                
                #==================================================================================
                #endregion Enable-SnsMfa
                #==================================================================================
                
                ##### Pass The Output Object To The Pipeline
                Write-Debug "Pass Output Object To The Pipeline";
                $PSCmdlet.WriteObject($objMfa);
            }
            
            If ($bolProgrBar)
            {
                Write-Progress -Activity "Enable-SnsMfa" -Id 1 -PercentComplete 100 -Verbose:$false -Debug:$false;
                Write-Progress -Activity "Enable-SnsMfa" -Id 1 -Completed -Verbose:$false -Debug:$false;
            }
        }
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intIn";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrMsolUsr";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objMfa";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolProgrBar";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolInteractive";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objAuthRequirement";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
    }
}

##### Export-SnsCredentialFile ====================================================
Function Export-SnsCredentialFile ()
{
<#
.SYNOPSIS
This CmdLet Creates Encrypted Password File.
.DESCRIPTION
This CmdLet Creates Encrypted Password File.
The CmdLet Is Intended To Interactively Prepare Credential File Of Another Account For Further Usage By Scripts
Executed As A Service On Schedule.
CmdLet Uses A Graphical User Interface (GUI) For Better User Experience.
The Produced Credential File Contains Information About When 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.
.INPUTS
Interactive Input Via Graphical User Interface.
.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-SnsCredentialFile;
.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";
        
        ##### Load The Asemblies And Initialize The Variables
        Write-Debug "Load The Asemblies And Initialize The Variables";
        [Void][Reflection.Assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089');
        [System.Management.Automation.PSCredential]$objCredentials = $null;
        [System.String]$strEncryptedPass = "";
        [System.Windows.Forms.SaveFileDialog]$objSaveFileDialog = $null;
        [System.String]$strPath = "";
        
        ##### Launch Get-Credential
        Write-Debug "Launch Get-Credential";
        [System.Management.Automation.PSCredential]$objCredentials = $null;
        [System.Management.Automation.PSCredential]$objCredentials = Get-Credential -Verbose:$false -Debug:$false;
        [System.String]$strEncryptedPass = "";
        [System.String]$strEncryptedPass = $objCredentials.Password | ConvertFrom-SecureString -Verbose:$false -Debug:$false;
        
        ##### Launch Save File Dialog
        Write-Debug "Launch Save File Dialog";
        [System.Windows.Forms.SaveFileDialog]$objSaveFileDialog = $null;
        [System.Windows.Forms.SaveFileDialog]$objSaveFileDialog = New-Object -TypeName "System.Windows.Forms.SaveFileDialog" -Verbose:$false -Debug:$false;
        $objSaveFileDialog.AddExtension = $true;
        $objSaveFileDialog.DefaultExt = "ini";
        $objSaveFileDialog.FileName = "$(""$($objCredentials.UserName)"".Replace('\','@@')).ini";
        $objSaveFileDialog.Title = "Save Secure Password File.";
        $objSaveFileDialog.Filter = "Information Configuration File (*.ini)|*.ini|All files (*.*)|*.*";
        $objSaveFileDialog.ValidateNames = $true;
        $objSaveFileDialog.ShowDialog();
        [System.String]$strPath = "";
        [System.String]$strPath = "$($objSaveFileDialog.FileName)";
        
        ##### Export The Encrypted Pass String On Credential File
        Write-Debug "Export The Encrypted Pass String On Credential File";
        If ([SnsPsModule.SnsCredentialFile]::Export("$($strEncryptedPass)", "$($strPath)"))
        {
            ##### Create And Launch The Message Window
            Write-Debug "Create And Launch The Message Window";
            [Void][System.Windows.Forms.MessageBox]::Show( `
                "Password Is Exported In:`r`n$($strPath)", "Information",
                [System.Windows.Forms.MessageBoxButtons]::"OK", [System.Windows.Forms.MessageBoxIcon]::"Information");
            #####
        }
        Else
        {
            ##### Create And Launch The Message Window
            Write-Debug "Create And Launch The Message Window";
            [Void][System.Windows.Forms.MessageBox]::Show( `
                "Failed To Export, Or The Export Contains Errors:`r`n$($strPath)", "Error",
                [System.Windows.Forms.MessageBoxButtons]::"OK", [System.Windows.Forms.MessageBoxIcon]::"Error");
            #####
        }
        
        ##### 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 "objSaveFileDialog";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEncryptedPass";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCredentials";
        
        [System.GC]::Collect(); Exit 0;
        Stop-Process -Id $pid -Force:$true -Confirm:$false -Verbose:$false -Debug:$false;
    }
}

##### Import-SnsCredentialFile ====================================================
Function Import-SnsCredentialFile ()
{
<#
.SYNOPSIS
This CmdLet Import An Encrypted Credential File Convert The Imported Value To PSCredentials Object And Verifies
The Output Existence
.DESCRIPTION
This CmdLet Import An Encrypted Credential File Convert The Imported Value To PSCredentials Object And Verifies
The Output Existence
In Case The Credential Object Generation 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 Can Import And Decrypt Properly Only Files Created Within The Security Context Of The Account Which
Executes The Function And Only On The Same Machine Where The File Was Created
The CmdLet Have Two Parameter Set:
-- UserAndFolder Unlike The Name Here Have To Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted Password File Resides
-- FullPath Here Have To Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To
Generate The UserName From The FileName
.PARAMETER UserName
Specifies The UserName
Parameter Set: UserAndFolder
Parameter Alias: User, Usr, UID, ID, Identity, FileName, Name
Parameter Validation: N/A
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File Resides
Parameter Set: UserAndFolder
Parameter Alias: Folder, FolderName, UNCPath, FolderFullName
Parameter Validation: Folder Existence Validation
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File
Parameter Set: FullPath
Parameter Alias: FullPath, FileFullPath, FileUNCPath
Parameter Validation: Yes, File Existence And File Name Validation
.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
.INPUTS
Global Variable [System.String]$global:StrScriptName - Contains The Name Of The Script To Be Used As Event Source
.OUTPUTS
[System.Management.Automation.PSCredential] Which Contains The PSCredential
.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.PSCredential]$objCredential = Import-SnsCredentialFile `
-UserName 'john.smith@contoso.com' -FolderPath 'C:\';
.EXAMPLE
[System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile `
-FilePath 'C:\john.smith@contoso.com.ini';
.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, ParameterSetName = "UserAndFolder", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("User", "Usr", "UID", "ID", "Identity", "FileName", "Name")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String[]]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "UserAndFolder", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("Folder", "FolderName", "UNCPath", "FolderFullName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FullPath", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $false)]
    [Alias("FullPath", "FileFullPath", "FileUNCPath")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith(".ini"))})]
    [ValidateScript({("$($_)".Contains("\"))})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String[]]$FilePath,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ScriptName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({((-not "$($_)") -or [SnsPsModule.SnsEventLog]::VerifySnsEventSource("$($_)"))})]
    [AllowNull()][AllowEmptyString()][System.String]$EventSource
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Import-SnsCredentialFile";
        Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)";
        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.Collections.Specialized.OrderedDictionary]$hashTemp = [Ordered]@{};
        [System.Int32]$intI = 0;
        [System.String]$strTemp = "";
        [System.Management.Automation.PSCredential]$objCredentials = $null;
        [System.Security.SecureString]$secStrPassword = $null;
    }
    
    ##### Override The Process Method
    Process
    {
        Write-Debug "Override Process Method";
        Write-Verbose "";
        
        #==================================================================================
        #region Generate The FilePath Hash
        #==================================================================================
        
        ##### Check The ParameterSetName
        Write-Debug "Check The ParameterSetName";
        [System.Collections.Specialized.OrderedDictionary]$hashTemp = [Ordered]@{};
        Switch ("$($PSCmdlet.ParameterSetName)")
        {
            "UserAndFolder"
            {
                ##### Normalize The FilePath Value
                Write-Debug "Normalize The FilePath Value";
                [System.String]$FolderPath = "$($FolderPath.TrimEnd(""\""))\";
                
                ##### Verify The UserName Input
                Write-Debug "Verify The UserName Input";
                If ($UserName.Count -gt 0)
                {
                    ##### Loop The Provided UserNames
                    Write-Debug "Loop The Provided UserNames";
                    [System.Int32]$intI = 0;
                    For ([System.Int32]$intI = 0; $intI -lt $UserName.Count; $intI++)
                    {
                        [System.String]$strTemp = "";
                        [System.String]$strTemp = "$($FolderPath)$($UserName[$intI].Replace(""\"",""@@"")).ini";
                        
                        ##### Verify Whether A Credential File For The Specified User Exists
                        Write-Debug "Verify Whether A Credential File For The Specified User Exists";
                        If (Test-Path -Path "$($strTemp)" -PathType "Leaf" -Verbose:$false -Debug:$false)
                        {
                            Write-Verbose "Enumerated Credential File: ""$($strTemp)"".";
                            $hashTemp.Add("$($UserName[$intI])", "$($strTemp)");
                        }
                        Else
                        {
                            [System.String]$strEventMessage = "Enumerated Credential File ""$($strTemp)"" Does Not Exists.";
                            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;
                        }
                    }
                }
            }
            
            "FullPath"
            {
                ##### Verify The FilePath Input
                Write-Debug "Verify The FilePath Input";
                If ($FilePath.Count -gt 0)
                {
                    ##### Loop The Provided FilePaths
                    Write-Debug "Loop The Provided FilePaths";
                    [System.Int32]$intI = 0;
                    For ([System.Int32]$intI = 0; $intI -lt $FilePath.Count; $intI++)
                    {
                        ##### Enumerate The UserName
                        Write-Debug "Enumerate The UserName";
                        [System.String]$strTemp = "";
                        [System.String]$strTemp = "$($FilePath[$intI].Split(""\"")[-1].Replace("".ini"",""""))";
                        If ("$($strTemp)" -like "*@@*") { [System.String]$strTemp = "$($strTemp.Replace(""@@"",""\""))"; }
                        
                        ##### Add The UserName To The UserName Array
                        Write-Verbose "Generated UserName: ""$($strTemp)""";
                        $hashTemp.Add("$($strTemp)", "$($FilePath[$intI])");
                    }
                }
            }
            
            default
            {
                [System.String]$strEventMessage = "Unknown ParameterSetName ""$($PSCmdlet.ParameterSetName)"".";
                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 Generate The FilePath Hash
        #==================================================================================
        
        #==================================================================================
        #region Generate The Credentials Object
        #==================================================================================
        
        ##### Verify The Input
        Write-Debug "Verify The Input";
        If ($hashTemp.Keys.Count -gt 0)
        {
            ##### Process Each SnsCredentialFile
            Write-Debug "Process Each SnsCredentialFile";
            [System.Int32]$intI = 0;
            For ([System.Int32]$intI = 0; $intI -lt $hashTemp.Keys.Count; $intI++)
            {
                ##### Verify The Credential File Existence
                [System.Management.Automation.PSCredential]$objCredentials = $null;
                If ( `
                    (-not -not "$($hashTemp.""$($hashTemp.Keys[$intI])"")") `
                    -and `
                    (Test-Path -Path "$($hashTemp.""$($hashTemp.Keys[$intI])"")" -PathType "Leaf" -Verbose:$false -Debug:$false) `
                )
                {
                    ##### Enumerate The Secure Password Variable
                    Write-Verbose "Import File: ""$($hashTemp.""$($hashTemp.Keys[$intI])"")"".";
                    [System.Security.SecureString]$secStrPassword = $null;
                    [System.Security.SecureString]$secStrPassword = [SnsPsModule.SnsCredentialFile]::Import("$($hashTemp.""$($hashTemp.Keys[$intI])"")") | ConvertTo-SecureString -Verbose:$false -Debug:$false;
                    
                    ##### Initialize The PSCredential Variable
                    Write-Debug "Initialize The PSCredential Variable";
                    [System.Management.Automation.PSCredential]$objCredentials = $null;
                    [System.Management.Automation.PSCredential]$objCredentials = New-Object -TypeName "System.Management.Automation.PSCredential" `
                        -ArgumentList @("$($hashTemp.Keys[$intI])", $secStrPassword) -Verbose:$false -Debug:$false;
                    #####
                }
                
                ##### Verify The Credentials Object Creation
                Write-Debug "Verify The Credentials Object Creation";
                If ("$($objCredentials.UserName)" -ne "$($hashTemp.Keys[$intI])")
                {
                    [System.String]$strEventMessage = "Failed To Generate The Credential Object";
                    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
                {
                    ##### Pass The Output Object To The Pipeline
                    Write-Debug "Pass Output Object To The Pipeline";
                    $PSCmdlet.WriteObject($objCredentials);
                }
            }
        }
        
        #==================================================================================
        #endregion Generate The Credentials Object
        #==================================================================================
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "secStrPassword";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCredentials";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strTemp";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hashTemp";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
    }
}

##### Invoke-SnsParallelJobs ======================================================
Function Invoke-SnsParallelJobs ()
{
<#
.SYNOPSIS
This CmdLet Creates And Manages PowerShell Jobs Thru Their Lifecycle To Have Multi-Threading Like Experience.
.DESCRIPTION
This CmdLet Creates And Manages PowerShell Jobs Thru Their Lifecycle To Have Multi-Threading Like Experience.
 
 
The Default PowerShell Session Is Single-Threaded. It Runs One Command And When It Finishes, It Moves To The Next
Command. This Is Nice As It Keeps Everything Repeatable And Does Not Use Many Resources. But What If The Actions
It Is Performing Are Not Dependent On One Another And You Have The CPU Resources To Spare? In That Case, It Is
Time To Start Thinking About Multi-Threading.
 
 
Multithreading Is A Way To Run More Than One Command At A Time. The Primary Benefit Of Multi-Threading Is To
Decrease The Runtime Of The Code. This Time Decrease Is At The Tradeoff A Higher Processing Power Requirement.
When Multi-Threading, Many Actions Are Being Performed At Once Thus Requiring More System Resources.
 
 
When Using Multi-Threading Have To Be Considered:
 
-- Code Optimization - Using A Code Which Is Not Performance Optimized Will Cause Excessive System Resource Usage.
The Effect Is Being Multiplied By The Number Of Threads Running In Parallel. Let Us Explain This With An Example:
 
Get-Process | Where-Object {$_.ProcessName -eq "PowerShell"}
 
Will Require Much More Resources And Time To Complete Than
 
Get-Process -Name "PowerShell"
 
Because The First Command Have To Enumerate All The System Processes, Process The Output And Send It To The
Pipeline Then The Second Command Have To Filter Out All The Processes Except "PowerShell", While In The Second
Example The Get-Process Command Will Enumerate Only System Processes With Name "PowerShell". The Same Result Is
Achieved With Less System Resources And Much Faster. In Case That Is Run In Multiple Threads This Will Be
Multiplied By Their Number.
 
-- Thread-Safe Code - The Code Run Within Multiple Threads Must Be Adapted For That. In Case Multiple Threads
Write To The Same File Or A DataBase Not Optimized For Handling Of The DataBase Locks Like Most Of The Free
Serverless DataBase Solutions, Might Lead To File Or DataBase Locks And Failure Of The Code Within The Threads And
Data Loss. In All The Cases The Updates In Those Files / DataBases Will Be Scrambled Because The Threads Run
Asynchronously And There Is No Guarantee That They Will Finish In The Order They Are Started. Writing Into A
Single Destination Is Considered As Thread-Unsafe Action, While Reading Is Considered As Thread-Safe Because All
The Threads Will Receive The Same Input. Dependency Of The Asynchronously Run Threads On Each Other Is Considered
As Thread-Unsafe As Well. This Ruins The Whole Point And Benefit Of Multi-Threading In Case A Thread Waits Another
Thread To Finish To Receive Its Output We Have No Gain In The Code Runtime The Code Is Executed Like In A Single
Thread One Command At A Time And The Next Is Run After The First One Finishes.
 
-- Performance Considerations - When Multi-Threading The Consumed System Resources Are Multiplied By The Number Of
Threads And Resources Are Used For The Threads Management Too. Running Multiple Threads Simultaneously Might
Quickly Consume The System Resources And Cause The System To Crash. To Prevent That The Number Of Simultaneously
Run Threads Have To Be Limited. According To The Best Practices The Number Of Threads Running In Parallel Should
Not Exceed The Number Of CPU Cores. When Exceeded We Have No Real Performance Gain As A CPU Have To Switch Between
The Threads. At The End, The Code Runtime Will Be Even Higher In Comparison With Limiting The Number Of Parallel
Running Threads.
 
 
 
We Can Have Multi-Threading In PowerShell In One Of Two Ways: Via Using PowerShell Jobs And Using PowerShell Run
Spaces. Each Of Those Represent A Single PowerShell Session And Is Single Threaded, However We Can Have Multiple
Of Those Managed From A Single PowerShell Session. Both Ways Multi-Threading Requires Complex Logic For Creation,
Monitoring, Output Extraction, Error Extraction And Disposal Of The Threads. Management Of PowerShell Jobs Is
Quite Different Than The Managing Of The PowerShell Run Spaces. The PowerShell Jobs And PowerShell Run Spaces Have
Their Advantages And Disadvantages. The PowerShell Spaces Cannot Be Managed Via PowerShell Directly, They Are
Managed From Within.NET, Which Makes Them Lighter Faster And Easier For Management. From Other Hand The PowerShell
Jobs Are Managed Using CmdLets Present In "Microsoft.PowerShell.Core" Module Distributed With The PowerShell Since
PowerShell v3.0. Although The PowerShell Jobs Are More Complicated In Comparison With The PowerShell Run Spaces,
Their Advantage Is That Multiple PowerShell CmdLets Can Automatically Make PowerShell Jobs On Your Behalf. The
PowerShell Job Still Remain On The Machine Where The Job Is Created, But The Job Itself Is Used Only To Manage The
Remote Session And Exchange Data Between The Job And The Remote Machine Such Like Sending Script Block And
Receiving Output.
 
 
Invoke-SnsParallelJobs CmdLet Is Intended To Automatically Manage PowerShell Jobs, Thus Simplifying The PowerShell
Multi-Threading Without Need To Care, About Jobs Creation, Number Of Simultaneously Running Jobs, Sending Of The
Jobs Code, Receiving Job Output And Errors And Jobs Disposal. The CmdLet Can Manage The PowerShell Jobs Created By
Itself, Any Existing PowerShell Jobs Remains. The Interactions That The CmdLet Might Have With Any Existing Jobs
Is To Resume / Restart Them, However They Might Greatly Reduce The CmdLet Performance If They Are Running, Because
The CmdLet Monitors The Running Jobs And Will Create Jobs For What Is Remaining Of The MaxThread Argument.
 
Invoke-SnsParallelJobs CmdLet Really Shine When You Need To Run A Single Piece Of Code On Multiple Remote
Computers. In That Case A List With Remote Computers Have To Be Provided As Argument To "JobName" Parameter.
Whenever You Need Simple Multi-Threading That Runs On The Same Machine To Decrease The Runtime Of Your Script,
Please Consider Using "Invoke-SnsParallelRunSpaces" CmdLet, It Might Provide Better Performance.
 
Invoke-SnsParallelJobs CmdLet Allows A Single Script Block To Be Specified To Multiple Threads, It Cannot Work
With Individual ScriptBlocks For The Treads. However It Allows Providing Individual Arguments To The Threads.
Since The ScriptBlock Is Specified By The User At The CmdLet Run Time, At The CmdLet Write Time Cannot Be
Estimated The Number Of, The Parameter Names And Parameter Types. Therefore It Is Not Possible They To Be Included
Into The CmdLet As CmdLet Parameters. The Way The Arguments Are Send To The Job, Is Using Splatting Hashtable. For
More Details How The Splatting Works Please Refer To The PowerShell Documentation "about_Splatting" Article:
 
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting
 
For Example If You Need To Run On Multiple Computers The Following Command:
 
Get-Process -Name "PowerShell" -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue";
 
You Will Need To Provide 4 Arguments To The Command Parameters, However This Cannot Be Known At
Invoke-SnsParallelJobs Development Time As It Will Be Different Each Time When The Command Is Run. Here The
Splatting Comes In Handy, As It Allows All The CmdLet Parameter Arguments To Be Included In A Single Hashtable
Where The Keys Are The Parameter Names And The Values Are The Arguments Provided To That Parameter Thus Way To The
Job Is Send Only One Argument (Hashtable).
 
[Hashtable]$arguments = `
@{
    "Name" = "PowerShell";
    "Verbose" = $false;
    "Debug" = $false;
    "ErrorAction" = "SilentlyContinue";
};
Get-Process @arguments;
 
Assuming That A List With Computer FQDNs Is Assigned In Advance To $arrComputers, When This Is Run On Multiple
Computers Using Invoke-SnsParallelJobs Will Looks Like:
 
$arrOutput = Invoke-SnsParallelJobs -JobName $arrComputers `
    -ScriptBlock 'Param($arguments) Return Get-Process @arguments;' `
    -JobArguments $arguments -Remote;
 
You Can Send The Job Name As Another Argument To The PowerShell Job. You Need To Include A Variable
$CurrentJobName In The ScriptBlock Parameters And Afterward It Can Be Used Within The ScriptBlock, Just Have To
Keep In Mind That $CurrentJobName Must Be The First Of The Two ScriptBlock Parameters:
 
$scrBlock = 'Param($CurrentJobName, $arguments) Return Get-Process -ComputerName $CurrentJobName @arguments;';
$arrOutput = Invoke-SnsParallelJobs -JobName $arrComputers -ScriptBlock $scrBlock -JobArguments $arguments -Remote;
 
It Is Quite Useful To Verify Whether The Remote Session Is Established On The Intended Destination Before To Do
Anything. Especially When The Destination Is Users Laptop. It Is Quite Common Those Days The Users To Work
Remotely And To Connect To The Corporate Environment Using VPNs. Which Means When Connected The Laptop Will
Register Its IP Address On The DNS Servers. On The Next Day, The Same IP Address Might Be Assigned To Another
Laptop, But Because Of The DNS Scavenging The DNS Entry Of The First Machine Will Remain At Least For The
Scavenging Period, If The First Machine Do Not Connect To The VPN It Wont Update Its DNS Entry With Its New IP
Address. In That Case When You Try To Establish A Remote Session To The First Machine You Will Receive The IP
Address Of The Second Machine And The Remote Session Will Be Established Against The Wrong One. If You Do Not Make
A Verification Within Your Code, You Will Make Changes On The Wrong Machine, Or The Received Output Will Be
Irrelevant. In That Case I Would Use:
 
$scrBlock = @'
Param($CurrentJobName, $arguments)
If ($CurrentJobName.ToLower().StartsWith("$([System.Environment]::MachineName.ToLower())."))
{
    Return Get-Process @arguments;
}
Else
{
    Write-Error "Connected To ""$([System.Environment]::MachineName)"" Instead Of ""$($CurrentJobName)""";
}
'@;
$arrOutput = Invoke-SnsParallelJobs -JobName $arrComputers -ScriptBlock $scrBlock -JobArguments $arguments -Remote;
 
Here We Throw An Error To Indicate The Issue. We Need That To Avoid Troubleshooting Missing Output.
 
In The Example Above The Same Set Of Arguments Is Provided To Each Of The Created Jobs. In Case You Need To
Specify Individual Arguments To Each Remote Job You Have To Provide An Array Of Splatting Hashtables Where Each
Member Corresponds To A Member From The Array Specified As Argument To "JobName" Parameter. The Both Arrays Must
Have The Same Length. To Avoid Issues With Mismatch Of The Positions In The Arrays In This Case Is Better To
Specify An FQDN And A Splatting Hashtable To The Pipeline Input Of The CmdLet In That Case They Are Send As Pairs
And There Is Not Any Chance Of Sending The Wrong Arguments To A Remote Job. For That Purpose
Invoke-SnsParallelJobs CmdLet Supports Only "ValueFromPipelineByPropertyName" Pipeline Input.
 
In Case The Remote Job ScriptBlock Have To Be Run On Multiple Machines Under Different Security Context,
Credential Object Can Be Provided As Argument To "Credential" Parameter. The Parameter Have The Same Limitation As
"JobArguments" Parameter, You Can Supply A Single PSCredential Object To All Remote Jobs Or Individual Credential
Objects For Each Of The Jobs. To Avoid Any Mismatch Between The Positions Of The Credential Objects And Machines
FQDN Preferably Supply Them Using The Pipeline. Any Mismatch Between The Length Of The Credential Array,
JobArguments Array And JobName Array Will Throw Terminating Errors.
Using Explicitly Supplied Credential Objects Allows The Same Script Block To Be Run Against Machines In Different
Domains And Not Domain Joined Machines At The Same Time. Just Have To Keep In Mind That The Supplied Credentials
Must Belong To Account Which Exists On The Corresponding Active Directory Domain, Or Locally On The Destination
Machine And Have Enough Access Rights To Establish Remote Session And Run Your Code.
 
Another Important Matter To Keep In Mind Is That The PowerShell Environment Variables, And The PowerShell
Automatic Modules Import Will Be Not Available And Will Not Work. For The Environment Variables You Can Use .NET
Ones Whenever They Corresponds To The Needed One (Like In The Example Above With The Destination Hostname
Verification). To Address The Lack Of Automatic Modules Load, You Shall Always Import Explicitly The Modules That
Contain The Commands You Need, Even Those That Are Distributed With PowerShell Itself, This Might Vary Depending
On The Destination Machine Operating System And PowerShell Version.
 
 
In Case You Decide To Play Around With Real Multi-Threading Where All The Thread Code Is Run Locally In Parallel
Using Invoke-SnsParallelJobs CmdLet, Ignoring My Recommendations About Using PowerShell Run Spaces, There Will Be
More Things To Be Considered. The CmdLet Uses Start-Job CmdLet To Create The Jobs In Background. That CmdLet
Introduces More Limitations. For Example Within The Script Block Cannot Be Used Certain CmdLets Like Import-Module
For Example. The ScriptBlock Also Have Limitation By Its Length. The Best Way To Maximize The Multi-Threading
Experience Is To Use "InitializationScript" Parameter Where You Can Add Your Custom Functions And Load Any
PowerShell Modules In Advance. Then The Real ScriptBlock Would Be Simple As Calling A Single Command With
Arguments. Even More You Can Save Your Custom Functions Into A Custom PowerShell Module And Import That Module
Into Jobs "InitializationScript". At The End You Are Making The Parallel Jobs On The Same Machine Which Means That
Custom Module Is Available To All Of Them Without Sending It To A Remote Machine Over The Same Session Or
Distribute It In Advance.
 
.PARAMETER ScriptBlock
Specifies The Code Which Have To Be Run Inside The Created Parallel Jobs.
 
The Job Code Can Be Provided In A ScriptBlock Or String, In The Second Case The CmdLet Will Convert It
Automatically To ScriptBlock. This Does Allow Dynamic Job Code Creation Depending On Results From Previous
Commands Within The Script, Which Normally Is Impossible As The Script Code Must Be Known At Script Writing
Time.
 
The ScriptBlock Will Run In Isolated Space Where Neither Defined Within The Script Variables Nor The Already
Imported PowerShell Modules Nor The Script Code Will Be Available. The Required Input Must Be Send To The Job
Space Using JobArguments As Splatting Hashtable. The Job Code Must Be Either Imported From Within The Job Or
Send To The Job. That Reflects To The Job Code Itself.
Example:
 
{
    Param($hshSplatt)
    Return Get-Process @hshSplatt;
}
 
Use The Predefined Variable $CurrentJobName Within The ScriptBlock Parameters And Within The Script Code, In
Case The JobName (Remote Machine FQDN) Is Needed Within The Job Code. The CmdLet Evaluates The Provided Job
Code And Includes The Current Job Name In The Sent Arguments If Its Usage Is Found At Least Twice (Once In The
Parameters And At Least Once In The Code). The Job Parameters Are Positional. $CurrentJobName Is Send At
Position 0. The Predefined Variable $CurrentJobName Must Be The First One In The Job Code Parameter Section.
Example:
 
{
    Param($CurrentJobName, $hshSplatt)
    If ($CurrentJobName.ToLower().StartsWith("$([System.Environment]::MachineName.ToLower())."))
    {
        #Do Something
    }
    Else
    {
        Write-Error "Error";
    }
}
 
Parameter Alias: "ScriptText", "JobScript" And "JobScriptText"
Parameter Set: All Parameter Sets
Parameter Validation: Yes Using Existence And Type Validation
 
.PARAMETER InitializationScript
Specifies The Code To Be Run Inside The Created Parallel Jobs Before To Be Started.
 
The Job Code Can Be Provided In A ScriptBlock Or String, In The Second Case The CmdLet Will Convert It
Automatically To ScriptBlock. This Does Allow Dynamic Job Code Creation Depending On Results From Previous
Commands Within The Script, Which Normally Is Impossible As The Script Code Must Be Known At Script Writing
Time.
 
Used When PowerShell Jobs Have To Run Their Code Locally In Parallel And The ScriptBlock Parameter Have
Limitations About The Commands Allowed To Be Run And Job Code Length. This Parameter Sends To The Job
Additional Code Which Will Be Executed Before The Job Is Started.
 
The CmdLet Import-Module Is Not Allowed In The Script Block, Instead All Modules And Job Code In Form Of
Functions Are Loaded In The Job Using "InitializationScript" Parameter. For Best Results Store Your Code In A
Custom PowerShell Module And Import It Using Import-Module In The "InitializationScript". In The Example We
Assume That The Custom Module Will Contain My-Command Which Will Call The Remaining CmdLets From The Custom
Module And Will Do The Required Tasks:
 
$arrOutput = Invoke-SnsParallelJobs -JobName $arrThreads -JobArguments $arguments -Local `
    -InitializationScript { Import-Module C:\Scripts\MyScript\MyModule.psm1; } `
    -ScriptBlock { Param($hshSplatt) Return My-Command @hshSplatt; };
 
 
Parameter Alias: "InitialScript", "InitialScriptText" And "InitialScriptBlock"
Parameter Set: LocalExecution
Parameter Validation: Yes Using Existence And Type Validation
 
.PARAMETER JobArguments
Specifies The Arguments To Send To The Job In Form Of Splatting Hashtable.
 
At The CmdLet Development Time The Arguments To Be Send To The Jobs Cannot Be Estimated. It Is Expected
Different ScriptBlock To Be Used On Each CmdLet Execution. This Makes Impossible Those Parameters To Be
Specified In The CmdLet. Thus The CmdLet Supports Sending Of Arguments Using Splatting Only. Splatting Is The
Only Possible Way To Send Unlimited Number Of Parameter Arguments In A Single Variable. For More Details
Please Refer To The PowerShell Documentation "about_Splatting" Topic.
 
The Parameter Accepts Either Single Hashtable To Be Send To All Of The PowerShell Jobs Or A Collection Of
Hashtables For Each Individual PowerShell Job. When Is Specified An Array With Hashtables The Number Of Array
Members Must Match The Number Of Jobs, And The Position Of Each Splatting Hashtable In The Array Must Match
The Position Of The JobName In The JobName Array. To Avoid Any Possible Issues Related With Mismatching
Positions In The Input Arrays Consider Sending The Input As Pairs Via PowerShell Pipeline. For More Details
Refer To The Examples Bellow.
 
Because The CmdLet Limitation Is Possible To Send Either Single Splatting Hashtable To All Jobs, Or Individual
Hashtables To Each Jobs, Or Not Send Any Arguments At All. Sending Of Multiple Arguments To Multiple Jobs
Where The Jobs Number Is Different Than The Arguments Hashtables Is Not Supported And The CmdLet Will Throw
An Error.
 
When Needed To Send Arguments To Part Of, And Not Sending Any Arguments To Other Part Of The Jobs Can Be Done
Via:
 
-- Prepare A Single Hashtable With Keys Corresponding To The JobNames And Values Splatting Hashtables For The
Corresponding Jobs. Send That Big Single Configuration Hashtable To All The Jobs Together With $CurrentJobName
Predefined Variable, And Within The Code Access The Value In The Pair That Corresponds To That Job.
Example:
 
$hshSplatJob1 = @{
    "Name" = "PowerShell";
    "Verbose" = $false;
    "Debug" = $false;
    "ErrorAction" = "SilentlyContinue";
};
 
$hshSplatJob2 = @{
    "Name" = "MsEdge";
    "Verbose" = $false;
    "Debug" = $false;
    "ErrorAction" = "SilentlyContinue";
};
 
$hshCfg = @{
    "Computer1" = $hshSplatJob1;
    "Computer2" = $hshSplatJob2;
};
 
$scrBlock = @'
    Param($CurrentJobName, $arguments)
    $hshSplatting = $arguments."$($CurrentJobName)";
    Return If (@hshSplatting.Keys.Count -gt 0) { Get-Process @hshSplatting; ) Else { Get-Process; }
'@;
 
$Jobs = @("Computer1", "Computer2", "Computer3");
 
$arrOutput = Invoke-SnsParallelJobs -JobName $Jobs -ScriptBlock $scrBlock -JobArguments $hshCfg -Remote;
 
-- Send The JobNames And The JobArguments Via The Pipeline Using "ValueFromPipelineByPropertyName". To The
Jobs That Need No Arguments Send $null Or Empty Hashtable As Job Argument. This Is The Best Approach To Avoid
Issues With Mismatching Positions Between The JobArguments, The JobNames And The Credential Objects In Their
Corresponding Arrays:
 
[PSCustomObject[]]$arrInput = [PSCustomObject[]]@();
[PSCustomObject[]]$arrInput += [PSCustomObject]@{
    "JobName" = "Computer1";
    "JobArguments" = @{ "Name" = "PowerShell"; "ErrorAction" = "SilentlyContinue"; };
    "Credential" = $Computer1Creds;
};
[PSCustomObject[]]$arrInput += [PSCustomObject]@{
    "JobName" = "Computer2";
    "JobArguments" = $null;
    "Credential" = $Computer2Creds;
};
[PSCustomObject[]]$arrInput += [PSCustomObject]@{
    "JobName" = "Computer2";
    "JobArguments" = @{};
    "Credential" = $null;
};
 
$scrBlock = @'
    Param($CurrentJobName, $arguments)
    $hshSplatting = $arguments."$($CurrentJobName)";
    Return If (@hshSplatting.Keys.Count -gt 0) { Get-Process @hshSplatting; ) Else { Get-Process; };
'@;
 
$arrOut = $arrInput | Invoke-SnsParallelJobs -ScriptBlock $scrBlock -Remote;
 
-- Specify $null Or Empty Hashtable In The Corresponding Positions In JobArguments Array:
 
$Jobs = @(
    "Computer1",
    "Computer2",
    "Computer3"
);
 
$Args = @(
    $null,
    @{},
    @{ "Name" = "PowerShell"; "ErrorAction" = "SilentlyContinue"; }
);
 
$scrBlock = @'
    Param($CurrentJobName, $arguments)
    $hshSplatting = $arguments."$($CurrentJobName)";
    Return If (@hshSplatting.Keys.Count -gt 0) { Get-Process @hshSplatting; ) Else { Get-Process; };
'@;
 
$arrOut = Invoke-SnsParallelJobs -JobName $Jobs -JobArguments $Args -ScriptBlock $scrBlock -Remote;
 
Parameter Alias: "ArgumentList", "Arguments" And "JobSplatting"
Parameter Set: All Parameter Sets
Parameter Validation: Yes Using Existence Validation
 
.PARAMETER JobName
Specifies Unique Names Of The Parallel Jobs.
 
The CmdLet Might Use The JobNames For Other Purposes Which Implies Requirements To The Job Names In Those
Scenarios. In Remote Job Execution Parameter Set The CmdLet Uses The JobName As Fully Qualified Domain Name
(FQDN) Of The Remote Machines To Establish Remote PSSessions And Run The ScriptBlock Within Them. Using IP
Addresses Works As Well Although It Does Not Provide Real Benefit, Because Have To Query The DNS System With
The FQDN To Obtain The IP Address In Advance Within Your Code. The Remote PSSession Establishing Will Do That
For You.
 
There Are Cases When Remote Resources Accessing Is Required And Remote Code Execution Is Not Possible. Such An
Example Would Be Accessing Dozens, Hundreds, Or Event Thousands Web Pages To Query, Analize And Compare
Information. The Best Approach In Those Cases Is To Use The Job Name Parameter To Provide URL's To The Job
ScriptBlock.
 
$Jobs = @(
    "https://site1.com/itemXXX",
    "https://site2.com/itemXXX",
    "https://site3.com/itemXXX"
);
 
$scrBlock = @'
    Param($CurrentJobName)
    [string]$webPage = Get-SnsWebPage -Url $CurrentJobName;
    Switch -Wildcard ($CurrentJobName)
    {
        "https://site1.com/*" { Return Get-Site1Price $webPage; }
        "https://site2.com/*" { Return Get-Site2Price $webPage; }
        "https://site3.com/*" { Return Get-Site3Price $webPage; }
        default { Write-Error "Unknown Web Page"; }
    }
'@;
 
$initScr = 'Import-Module SnsPsModule; Import-Module C:\Scripts\MyScript\MyModule;';
 
$arrOut = Invoke-SnsParallelJobs -JobName $Jobs -ScriptBlock $scrBlock -InitializationScript $initScr -Local;
 
Consider Using The Same Approach When Job Specific Information Is Needed Within The Job.
 
Parameter Alias: "Job", "Computer", "ComputerName", "HostName" And "FQDN"
Parameter Set: All Parameter Sets
Parameter Validation: Yes Using Existence And Uniqueness Validation
 
.PARAMETER Credential
Specifies PSCredential Object Used To Establish A Remote Session To A Remote Machine.
 
The Parameter Accepts Either Single PSCredential Object To Be Send To All Of The PowerShell Jobs Or A
Collection Of PSCredential Objects For Each Individual PowerShell Job. When Is Specified An Array With
PSCredential Objects The Number Of Array Members Must Match The Number Of Jobs, And The Position Of Each
PSCredential Object In The Array Must Match The Position Of The Remote Computer FQDN In The JobName Array. To
Avoid Any Possible Issues Related With Mismatching Positions In The Input Arrays Consider Sending The Input As
Pairs Via PowerShell Pipeline. For More Details Refer To "JobArguments" Parameter Examples.
 
When No PSCredential Object Is Specified To All Or Specific Job The CmdLet Will Attempt To Establish The
Remote Session Under Current User Security Context.
 
Parameter Alias: N/A
Parameter Set: RemoteExecution
Parameter Validation: Yes Using Existence Validation
 
.PARAMETER MaxThreads
Specifies The Maximum Number Of Running Jobs. When The Running Threads Reach The Threshold The CmdLet Will
Pause The Creation Of New Jobs Until An Already Created Ones Finish. Thus Way At Any Given Time The Running
Jobs Will Be Equal Or Bellow The Threshold. Multi-Threading Is A Hardware Intensive, When Overdone Might Lead
To Exhausting Machines Resources And Crashes. Running More Threads Than The Number Of CPU's Makes No Sense.
The Processors Will Need To Work On Multiple Threads Simultaneously Switching Between Them. This Does Not
Provide Any Performance Benefits.
 
When Omitted The CmdLet Will Evaluate The Number Of Logical CPU Cores On The Computer And Will Use This As
MaxThreads Threshold. It Is Highly Recommended To Leave The CmdLet To Evaluate This Threshold By Itself.
 
When The Jobs Code Is Run On A Remote Machine, The Local Jobs Maintain The Remote Sessions And Exchange Data
Over Those Sessions. It Actually Does Not Load The Hardware As Much As Compared With Locally Executed Code. In
This Case The CmdLet Evaluates The Number Of Logical CPU Cores On The Computer, Multiply Their Number By
Sixteen (16) And Use The Result As MaxThreads Threshold.
 
When The Threshold Is Manually Specified In RemoteExecution, The CmdLet Will Not Multiply The User Specified
MaxThreads Value By 16. The Threshold Will Always Be The Value Specified By The User. More Details About What
The CmdLet Evaluated As MaxThreads Threshold And The Way The Jobs Are Created And Managed Can Be Seen In The
CmdLet Verbose Stream.
 
Parameter Alias: N/A
Parameter Set: All Parameter Sets
Parameter Validation: Yes Using Range Validation
 
.PARAMETER MaxDuration
Specifies The Maximum Time In Minutes To Allow The Jobs To Complete Or Fail. Multi-Threading Is Intended To
Reduce The Code Runtime But A Tread Might Become Stuck Or Take Excessive Time To Complete. "MaxDuration"
Parameter Limits The Running Time Of The Jobs. In Case Jobs Are Not Completed Or Failed At The End Of The
Period, The CmdLet Extracts Any Output Generated So Foar And Stops The Jobs.
 
Like Every Other PowerShell CmdLet Invoke-SnsParallelJobs Returns The Control To The PowerShell Sessions After
It Finishes. This Threshold Does Allow The CmdLet To Revert The Output Extracted So Far And Return The Control
At The End Of The Specified Period Of Time, Without To Interrupt It. It Is Recommended To Test The Code
Runtime Before To Set That Threshold And Allow Enough Time The Code To Complete. Consider Increasing Of
MaxDuration Threshold When Large Number Of Threads Are Needed.
 
Parameter Alias: N/A
Parameter Set: All Parameter Sets
Parameter Validation: Yes Using Range Validation
 
.PARAMETER Local
Specifies To The CmdLet To Execute The Jobs Code Locally.
 
Parameter Alias: "LocalExecution"
Parameter Set: "LocalExecution"
Parameter Validation: N/A
 
.PARAMETER Remote
Specifies To The CmdLet To Execute The Jobs Code On Remote Machines. Implies Additional Requirements To
"JobName" Parameter Arguments When Used. The JobName Values Must Correspond To Remote Computers Either FQDN's
Or Hostnames Or IP Addresses. The Usage Of FQDN's And Hostnames Will Depend On Your DNS / WINS System
Availability And Health.
 
Parameter Alias: "RemoteExecution"
Parameter Set: "RemoteExecution"
Parameter Validation: N/A
 
.INPUTS
JobName <String[]>, JobArguments <Hashtable[]> And Credential <PSCredential[]> Using
"ValueFromPipelineByPropertyName" Pipeline Input.
.OUTPUTS
A Collection With [SnsPsModule.SnsThreadStatus[]] Objects, Which Contains The Job Names, The Extracted Jobs Output
And The Extracted From The Jobs Code Execution Errors. Those Are Not Errors That The Jobs Might Experience. Those
Are Errors Thrown By The Job Code Inside The Jobs. When The Job Finished With Status Different Than "Completed"
And No Extracted Job Errors, The Job Status Will Be Added As Job Error.
.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
$LdapQry = "(&(objectCategory=Computer)(objectClass=computer)(operatingSystem=Windows Server*))";
 
# Initialize LDAP Query String Variable To Query About All Windows Server Domain Joined Computers.
 
 
$arrSrcResults = Search-SnsAdObject -ReturnProperties "dNSHostName" -LdapQuery $LdapQry;
 
#Runs The LDAP Query Against The Current Domain
 
 
$arrServerFqdns = $arrSrcResults | `
    Select-Object -Property @(@{ "n" = "Name"; "e" = { "$($_.Properties.dnshostname)" }; }) | `
    Select-Object -ExpandProperty "Name";
 
# Process The Search Results Collection To A String Array With Servers FQDNs.
 
 
$scrBlock = @'
    # Do Some Health Checks
    Return $HealthChecksOutput;
'@;
 
# Initialize A String Variable With PowerShell Code Which Do A Health Check On The Machine Where It Is Run.
 
 
$arrServersHealth = Invoke-SnsParallelJobs -JobName $arrServerFqdns -ScriptBlock $scrBlock -Remote;
 
# Creates Parallel Jobs Which Run Their Code Remotely On The Corresponding Machines
# And Retrieve The AD Joined Windows Servers Health.
# In Order This To Work As Expected The Account Under Which Security Context Is Started
# The PowerShell Must Have Sufficient Permissions To Connect To The Destination Servers WinRM.
# Because The Servers Array Contains Domain Controllers The CmdLet Must Be Run By Domain Admin.
# The Maximum Running Jobs At A Time Is The Number Of Logical CPU Cores Multiplied By 16
 
.EXAMPLE
$LdapQry = "(&(objectCategory=Computer)(objectClass=computer)(operatingSystem=Windows Server*))";
 
# Initialize LDAP Query String Variable To Query About All Windows Server Domain Joined Computers.
 
 
$arrSrcResults = Search-SnsAdObject -ReturnProperties "dNSHostName" -LdapQuery $LdapQry;
 
#Runs The LDAP Query Against The Current Domain
 
 
$arrServerFqdns = $arrSrcResults | `
    Select-Object -Property @(@{ "n" = "Name"; "e" = { "$($_.Properties.dnshostname)" }; }) | `
    Select-Object -ExpandProperty "Name";
 
# Process The Search Results Collection To A String Array With Servers FQDNs.
 
 
[System.Collections.Hashtable]$hshArguments = @{
    "ExludedServices" = @("edgeupdate", "gupdate", "RemoteRegistry", "sppsvc");
}
# Initialize A Hashtable With Just One Key-Value Pair
# We Need To Send String Collection But We Can Send Hashtable Only
 
 
$scrBlock = @'
    Param($hshArguments)
    Return Get-CimInstance -Namespace "root/cimv2" -ClassName "Win32_Service" `
        -Property @( "DisplayName", "Name", "Caption", "Description", "PathName" ) `
        -Filter "StartMode = 'Auto' AND State != 'Running'" | `
        Where-Object {$hshArguments.ExludedServices -notcontains "$($_.Name)"};
'@;
 
# Initialize A String Variable With Code Alligned To Be Run On Remote Machine
 
 
$arrNotRunningServices = Invoke-SnsParallelJobs -JobName $arrServerFqdns -ScriptBlock $scrBlock `
    -JobArguments $hshArguments -Remote -Verbose;
 
# Creates Parallel Jobs Which Run Their Code Remotely On The Corresponding Machines
# And Retrieve The Not Running Automatic Services.
# In Order This To Work As Expected The Account Under Which Security Context Is Started
# The PowerShell Must Have Sufficient Permissions To Connect To The Destination Servers WinRM.
# Because The Servers Array Contains Domain Controllers The CmdLet Must Be Run By Domain Admin.
# The Maximum Running Jobs At A Time Is The Number Of Logical CPU Cores Multiplied By 16
# In Case Of Unreachable Machines In The Error Property Of The Output Object Will Have "Job Failed."
# In This Case The Job Actually Did Not Run The Code And There Were No Errors
# Instead The Job State Is Reverted As Job Error
# There Is No Meaningful Reason For The Job Failure, And No Troubleshooting Information.
# We Know That A Specific Machine Is Shut Down In Advance.
 
.EXAMPLE
$LdapQry = "(&(objectCategory=Computer)(objectClass=computer)(operatingSystem=Windows Server*))";
 
# Initialize LDAP Query String Variable To Query About All Windows Server Domain Joined Computers.
 
 
$arrSrcResults = Search-SnsAdObject -ReturnProperties "dNSHostName" -LdapQuery $LdapQry;
 
#Runs The LDAP Query Against The Current Domain
 
 
$arrServerFqdns = $arrSrcResults | `
    Select-Object -Property @(@{ "n" = "Name"; "e" = { "$($_.Properties.dnshostname)" }; }) | `
    Select-Object -ExpandProperty "Name";
 
# Process The Search Results Collection To A String Array With Servers FQDNs.
 
 
[System.Collections.Hashtable]$hshArguments = @{
    "ExludedServices" = @("edgeupdate", "gupdate", "RemoteRegistry", "sppsvc");
}
# Initialize A Hashtable With Just One Key-Value Pair
# We Need To Send String Collection But We Can Send Hashtable Only
 
 
$scrBlock = @'
    Param($CurrentJobName, $hshArguments)
    Return Get-CimInstance -Computer $CurrentJobName -Namespace "root/cimv2" -ClassName "Win32_Service" `
        -Property @( "DisplayName", "Name", "Caption", "Description", "PathName" ) `
        -Filter "StartMode = 'Auto' AND State != 'Running'" | `
        Where-Object {$hshArguments.ExludedServices -notcontains "$($_.Name)"};
'@;
 
# Initialize A String Variable With Code Alligned To Be Run Locally.
# The Required Remote Session Is Handled Within The Code.
# In This Case By Get-CimInstance Command.
 
 
$arrNotRunningServices = Invoke-SnsParallelJobs -JobName $arrServerFqdns -ScriptBlock $scrBlock `
    -JobArguments $hshArguments -Local -Verbose;
 
# Creates Parallel Jobs Which Run Their Code Locally And Retrieve The Not Running Automatic Services.
# Basically IT Does The Same As The Previous Example With Key Differences:
# -- The Job Code Runs Locally.
# -- The Running At Any Given Time Is The Number Of The CPU Logical Cores.
# -- It Requires More Runtime Because Of The Reduced Number Of Threads.
# -- It Requires More System Recourses Because The Code Is Run Locally.
# -- In Case OF Unreachable Machine We Have Error Thrown By Get-CimInstance Command.
# The Error Is: "WinRM cannot complete the operation. Verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer. By default, the WinRM firewall exception for public profiles limits access to remote computers within the same local subnet."
# This Is Much More Meaningful Error Which Does Provide Starting Point For Troubleshooting.
 
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "RemoteExecution")]
Param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("ScriptText", "JobScript", "JobScriptText")]
    [ValidateNotNullOrEmpty()][System.Object]$ScriptBlock,
    
    [Parameter(Mandatory = $false, ParameterSetName = "LocalExecution", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("InitialScript", "InitialScriptText", "InitialScriptBlock")]
    [ValidateNotNullOrEmpty()][System.Object]$InitializationScript,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("ArgumentList", "Arguments", "JobSplatting")]
    [AllowNull()][AllowEmptyCollection()][ValidateCount(0,32767)][System.Collections.Hashtable[]]$JobArguments,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("Job", "Computer", "ComputerName", "HostName", "FQDN")]
    [ValidateNotNullOrEmpty()][ValidateCount(1,32767)][ValidateScript({$_.Count -eq (@($_ | Select-Object -Unique)).Count})][System.String[]]$JobName,
    
    [Parameter(Mandatory = $false, ParameterSetName = "RemoteExecution", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [AllowNull()][AllowEmptyCollection()][ValidateCount(0,32767)][System.Management.Automation.PSCredential[]]$Credential,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][ValidateRange(1,255)][System.Int16]$MaxThreads = 1,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][ValidateRange(1,32767)][System.Int32]$MaxDuration = 40,
    
    [Parameter(Mandatory = $true, ParameterSetName = "LocalExecution", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("LocalExecution")]
    [Switch]$Local,
    
    [Parameter(Mandatory = $true, ParameterSetName = "RemoteExecution", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Alias("RemoteExecution")]
    [Switch]$Remote
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose "";
        Write-Verbose "Invoke-SnsParallelJobs";
        Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)";
        Write-Verbose "ErrorAction: $($ErrorActionPreference)";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        ##### 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; }
        
        ##### Verify Whether The User Did Not Specified MaxThreads
        Write-Debug "Verify Whether The User Did Not Specified MaxThreads";
        If ($PSCmdlet.MyInvocation.BoundParameters.Keys -inotcontains "MaxThreads")
        {
            [System.Int16]$MaxThreads = [System.Int16]"$(If ([System.Environment]::ProcessorCount -gt 2047) { 2047; } Else { [System.Environment]::ProcessorCount; })";
            If ($MaxThreads -le 0) { [System.Int16]$MaxThreads = 4; }
            If ("$($PSCmdlet.ParameterSetName)" -eq "RemoteExecution") { [System.Int16]$MaxThreads = $MaxThreads * 16; }
            Write-Verbose "MaxThreads: $($MaxThreads)";
        }
        
        ##### Verify Whether The Latest SnsPsModule Is Loaded
        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"; };
        
        #==================================================================================
        #region Inner Functions
        #==================================================================================
        
##### Get-SnsJobStatusAndOutput ===================================================
Function Get-SnsJobStatusAndOutput ()
{
<#
.SYNOPSIS
This CmdLet Extracts The Output And The Errors From A Specified Job And Deletes It.
.DESCRIPTION
This CmdLet Extracts The Output And The Errors From A Specified Job And Deletes It.
In Case The Specified Job Is Not Completed It Is Stopped.
.PARAMETER Job
Specifies The Job Object.
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER RemoveJob
Specifies To Remove The Job Object After The Output Extraction.
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
The CmdLet Does Not Support Pipeline Input.
.OUTPUTS
[SnsPsModule.SnsThreadStatus] With Properties About The Job Name, The Extracted Job Output And Job Errors If Any.
.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
[SnsPsModule.SnsThreadStatus]$objJobStatus = Get-SnsJobStatusAndOutput -Job $objJob -RemoveJob:$true;
#>

[CmdletBinding(PositionalBinding = $false)]
Param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][System.Object]$Job,
    
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [System.Boolean]$RemoveJob
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        
        ##### Initialize The Output Object
        Write-Debug "Initialize The Output Object";
        [SnsPsModule.SnsThreadStatus]$objJobStatus = New-Object -TypeName "SnsPsModule.SnsThreadStatus";
        $objJobStatus.Name = "$($Job.Name)";
        
        ##### Verify Whether The Job Status Is Completed
        Write-Debug "Verify Whether The Job Status Is Completed";
        If ("$($Job.State)" -eq "Completed")
        {
            ##### Retrieve The Output From The Job
            Write-Debug "Retrieve The Output From Job ""$($Job.Name)""";
            $objJobStatus.Output = Receive-Job -Id $Job.Id -Keep:$true -ErrorAction "SilentlyContinue" -Verbose:$false -Debug:$false;
        }
        
        ##### Verify About Job Errors And Add Them To The Output Object
        Write-Debug "Verify About Job Errors And Add Them To The Output Object";
        If (-not -not "$($Job.Error)") { $objJobStatus.Errors += "$($Job.Error)"; }
        If (-not -not "$($Job.ChildJobs[0].Error)") { $objJobStatus.Errors += "$($Job.ChildJobs[0].Error)"; }
        
        ##### Verify Whether The Job Status Is Different Than Completed And There Are No Errors
        Write-Debug "Verify Whether The Job Status Is Different Than Completed And There Are No Errors";
        If (("$($Job.State)" -ne "Completed") -and ($objJobStatus.Errors.Count -le 0)) { $objJobStatus.Errors += "Job $($Job.State)."; }
        
        ##### Verify Whether The Job Have To Be Removed
        Write-Debug "Verify Whether The Job Have To Be Removed";
        If ($RemoveJob)
        {
            ##### Stop The Job Just In Case
            Write-Debug "Stop The Job Just In Case";
            Stop-Job -Id $Job.Id -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false | Out-Null;
            
            ##### Remove The Job To Clean Up The Session
            Write-Debug "Remove The Job To Clean Up The Session";
            Remove-Job -Id $Job.Id -Force:$true -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false | Out-Null;
        }
        
        $PSCmdlet.MyInvocation.BoundParameters | Select-Object -ExpandProperty "Keys" | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        Return $objJobStatus;
    }
}
        
##### Get-SnsIncompleteJobs =======================================================
Function Get-SnsIncompleteJobs ()
{
<#
.SYNOPSIS
This CmdLet Enumerates All Jobs With Status Different Than Completed And Failed.
.DESCRIPTION
This CmdLet Enumerates All Jobs With Status Different Than Completed And Failed.
.PARAMETER CreatedJobs
Specifies The Job Objects For The Jobs Created By This CmdLet.
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
The CmdLet Does Not Support Pipeline Input.
.OUTPUTS
[System.Array] With Enumerate Incomplete Job 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]$arrIncompleteJobs = Get-SnsIncompleteJobs -CreatedJobs @();
#>

[CmdletBinding(PositionalBinding = $false)]
Param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [AllowEmptyCollection()][System.Array]$CreatedJobs
)
    ##### Override The Begin Method
    Begin
    {
        [System.Array]$arrJobs = Get-Job -Verbose:$false -Debug:$false | Where-Object {("$($_.State)" -ne "Completed") -and ("$($_.State)" -ne "Failed")} -Verbose:$false -Debug:$false;
        If ($CreatedJobs.Count -gt 0) { Return $arrJobs | Where-Object {$CreatedJobs.Id -icontains $_.Id} -Verbose:$false -Debug:$false; } Else { Return $arrJobs; }        
    }
}
        
        #==================================================================================
        #endregion Inner Functions
        #==================================================================================
        
        ##### Initialize The Variables
        [System.Boolean]$bolRemoveCompleted = $true;
        [System.Management.Automation.ScriptBlock]$objScrBlock = $null;
        [System.Management.Automation.ScriptBlock]$objIniScrBlock = $null;
        [System.Int32]$intI = 0;
        [System.Collections.Hashtable[]]$arrInputObjects = @();
        [System.Boolean]$bolProgrBar = $false;
        [System.DateTime]$datThreshold = [System.DateTime]::FromFileTime(0);
        [System.Int32]$intCompletedJobs = 0;
        [System.Array]$arrCreatedJobs = @();
        [System.Collections.Hashtable]$hshSplatting = @{};
        [System.Array]$arrArgs = @();
        [System.Array]$arrCurrentJobs = @();
        [SnsPsModule.SnsThreadStatus[]]$arrOutput = @();
        
        #==================================================================================
        #region Pre Process The Provided Input
        #==================================================================================
        
        ##### Process The ScriptBlock Input Object
        Write-Debug "Process The ScriptBlock Input Object";
        [System.Management.Automation.ScriptBlock]$objScrBlock = $null;
        If (-not -not "$($ScriptBlock)")
        {
            ##### Verify Whether ScriptBlock Is Provided As Text Or Object
            Write-Debug "Verify Whether ScriptBlock Is Provided As Text Or Object";
            If ("$((@($ScriptBlock | Get-Member))[0].TypeName)" -eq "System.String")
            {
                ##### Create The ScriptBlock Object
                Write-Debug "Create The ScriptBlock Object";
                [System.Management.Automation.ScriptBlock]$objScrBlock = [System.Management.Automation.ScriptBlock]::Create("$($ScriptBlock)");
                Write-Verbose "Converted ScriptBlock Argument To ScriptBlock Object:`r`n$($objScrBlock)";
            } ElseIf ("$((@($ScriptBlock | Get-Member))[0].TypeName)" -eq "System.Management.Automation.ScriptBlock")
            {
                ##### Assign The Specified ScriptBlock
                Write-Debug "Using The Argument Provided With Parameter ScriptBlock";
                [System.Management.Automation.ScriptBlock]$objScrBlock = $ScriptBlock;
            } Else
            {
                Write-Error -Message "Invalid ScriptBlock Argument: ""$($ScriptBlock)""" -ErrorAction "Stop";
            }
        }
        
        ##### Verify The ScriptBlock Initialization
        Write-Debug "Verify The ScriptBlock Initialization";
        If (-not "$($objScrBlock)") { Write-Error -Message "Failed To Initialize The ScriptBlock ScriptBlock Object" -ErrorAction "Stop"; }
        
        ##### Process The InitializationScript Input Object
        Write-Debug "Process The InitializationScript Input Object";
        [System.Management.Automation.ScriptBlock]$objIniScrBlock = $null;
        If (-not -not "$($InitializationScript)")
        {
            ##### Verify Whether InitializationScript Is Provided As Text Or Object
            Write-Debug "Verify Whether InitializationScript Is Provided As Text Or Object";
            If ("$((@($InitializationScript | Get-Member))[0].TypeName)" -eq "System.String")
            {
                ##### Create The InitializationScript Object
                Write-Debug "Create The InitializationScript Object";
                [System.Management.Automation.ScriptBlock]$objIniScrBlock = [System.Management.Automation.ScriptBlock]::Create("$($InitializationScript)");
                Write-Verbose "Converted InitializationScript Argument To ScriptBlock Object:`r`n$($objIniScrBlock)";
            }
            ElseIf ("$((@($InitializationScript | Get-Member))[0].TypeName)" -eq "System.Management.Automation.ScriptBlock")
            {
                Write-Debug "Using The Argument Provided With Parameter InitializationScript";
                [System.Management.Automation.ScriptBlock]$objIniScrBlock = $InitializationScript;
            } Else
            {
                Write-Error -Message "Invalid InitializationScript Argument: ""$($InitializationScript)""" -ErrorAction "Stop";
            }
            
            ##### Verify The InitializationScript Initialization
            Write-Debug "Verify The InitializationScript Initialization";
            If (-not "$($objIniScrBlock)") { Write-Error -Message "Failed To Initialize The InitializationScript ScriptBlock Object" -ErrorAction "Stop"; }
        }
        
        #==================================================================================
        #endregion Pre Process The Provided Input
        #==================================================================================
    }
    
    ##### Override The Process Method
    Process
    {
        Write-Debug "Override Process Method";
        
        ##### Validate The JobArguments And Credential Input
        Write-Debug "Validate The JobArguments And Credential Input";
        If (($JobArguments.Count -gt 1) -and ($JobArguments.Count -ne $JobName.Count)) { Write-Error -Message "Invalid JobArguments Argument" -ErrorAction "Stop"; }
        If ($JobArguments.Count -eq 0) { [System.Collections.Hashtable[]]$JobArguments = @( @{} ); }
        If (($Credential.Count -gt 1) -and ($Credential.Count -ne $JobName.Count)) { Write-Error -Message "Invalid Credential Argument" -ErrorAction "Stop"; }
        If ($Credential.Count -eq 0) { [System.Management.Automation.PSCredential[]]$Credential = @( [System.Management.Automation.PSCredential]$null ); }
        
        ##### Verify Whether There Are Input Objects
        Write-Debug "Verify Whether There Are Input Objects";
        If ($JobName.Count -gt 0)
        {
            ##### Loop Each Specified JobName
            Write-Debug "Loop Each Specified JobName";
            [System.Int32]$intI = 0;
            For ([System.Int32]$intI = 0; $intI -lt $JobName.Count; $intI++)
            {
                ##### Initialize A Hashtable For The Current Job Coming From The Pipeline
                Write-Debug "Initialize A Hashtable For The Current Job Coming From The Pipeline";
                [System.Collections.Hashtable[]]$arrInputObjects += `
                @{
                    "JobName" = "$($JobName[$intI])";
                    "JobArguments" = $( If ($JobArguments.Count -gt 1) { $JobArguments[$intI]; } Else { $JobArguments[0]; } );
                    "Credential" = $( If ($Credential.Count -gt 1) { $Credential[$intI]; } Else { $Credential[0]; } );
                };
            }
        }
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        #==================================================================================
        #region Parallel Jobs Processing
        #==================================================================================
        
        ##### In Case No Input Objects Exit The CmdLet
        If ($arrInputObjects.Count -eq 0) { Write-Error -Message "Missing JobName Argument." -ErrorAction "Stop"; }
        
        ##### Enumerate The Prerequisites
        Write-Debug "Enumerate The Prerequisites";
        [System.Boolean]$bolProgrBar = [System.Environment]::UserInteractive -and ($arrInputObjects.Count -gt 5);
        [System.DateTime]$datThreshold = [System.DateTime]::Now.AddMinutes($MaxDuration);
        
        ##### Loop The Parallel Jobs Creation And Verification
        Write-Debug "Loop The Parallel Jobs Creation And Verification";
        Do
        {
            Write-Verbose ""; Write-Verbose ""; Write-Verbose ""; Write-Verbose "$(""-"" * 70)";
            Write-Verbose "$([System.DateTime]::Now.ToString(""yyyy-MM-dd HH:mm:ss""))"; Write-Verbose "";
            
            ##### Evaluate The Number Of Input Objects
            If ($bolProgrBar) { Write-Progress -Activity "Invoke-SnsParallelJobs" -Id 1 -PercentComplete (($intCompletedJobs * 100) / $arrInputObjects.Count) -Verbose:$false -Debug:$false; }
            
            #==================================================================================
            #region Parallel Jobs Creation
            #==================================================================================
            
            ##### Loop The Jobs Creation
            Write-Debug "Loop The Jobs Creation";
            While (($arrCreatedJobs.Count -lt $arrInputObjects.Count) -and ((@(Get-Job -State "Running" -Verbose:$false -Debug:$false)).Count -lt $MaxThreads))
            {
                ##### Initialize A Splatting Hashtable
                Write-Debug "Initialize A Splatting Hashtable";
                [System.Collections.Hashtable]$hshSplatting = @{ "ScriptBlock" = $objScrBlock; "Verbose" = $false; "Debug" = $false; }
                
                ##### Add The HostName To The Job Argument List If Requested
                Write-Debug "Add The HostName To The Job Argument List If Requested";
                [System.Array]$arrArgs = @();
                If ("$($objScrBlock)".TrimStart() -like "Param*`$CurrentJobName*`$CurrentJobName*") { [System.Array]$arrArgs += "$($arrInputObjects[$arrCreatedJobs.Count].JobName)"; }
                
                ##### Verify Whether The Job Script Splatting Have Keys
                Write-Debug "Verify Whether The Job Script Splatting Have Keys";
                If ($arrInputObjects[$arrCreatedJobs.Count].JobArguments.Keys.Count -gt 0) { [System.Array]$arrArgs += $arrInputObjects[$arrCreatedJobs.Count].JobArguments; }
                
                ##### Verify Whether Arguments Have To Be Send To The Job
                Write-Debug "Verify Whether Arguments Have To Be Send To The Job";
                If ($arrArgs.Count -gt 0) { $hshSplatting.Add("ArgumentList", $arrArgs); }
                
                ##### Verify The ParameterSetName
                Write-Debug "Verify The ParameterSetName";
                Write-Verbose "";
                Write-Verbose "Create Job For ""$($arrInputObjects[$arrCreatedJobs.Count].JobName)""";
                Switch ("$($PSCmdlet.ParameterSetName)")
                {
                    "RemoteExecution"
                    {
                        ##### Add Required Parameters To The Splatting Hashtable
                        Write-Debug "Add Required Parameters To The Splatting Hashtable";
                        $hshSplatting.Add("ComputerName", "$($arrInputObjects[$arrCreatedJobs.Count].JobName)");
                        $hshSplatting.Add("AsJob", $true);
                        $hshSplatting.Add("JobName", "$($arrInputObjects[$arrCreatedJobs.Count].JobName)");
                        If (-not -not "$($arrInputObjects[$arrCreatedJobs.Count].Credential.UserName)") { $hshSplatting.Add("Credential", $arrInputObjects[$arrCreatedJobs.Count].Credential); }
                        
                        ##### Create New Job
                        Write-Debug "Create New Job";
                        [System.Array]$arrCreatedJobs += Invoke-Command @hshSplatting;
                        
                        Break;
                    }
                    
                    "LocalExecution"
                    {
                        ##### Add Required Parameters To The Splatting Hashtable
                        Write-Debug "Add Required Parameters To The Splatting Hashtable";
                        $hshSplatting.Add("Name", "$($arrInputObjects[$arrCreatedJobs.Count].JobName)");
                        If (-not -not "$($objIniScrBlock)") { $hshSplatting.Add("InitializationScript", $objIniScrBlock); }
                        
                        ##### Create New Job
                        Write-Debug "Create New Job";
                        [System.Array]$arrCreatedJobs += Start-Job @hshSplatting;
                        
                        Break;
                    }
                    
                    default { Write-Error -Message "Unrecognized ParameterSetName: ""$($PSCmdlet.ParameterSetName)""" -ErrorAction "Stop"; }
                }
            }
            
            #==================================================================================
            #endregion Parallel Jobs Creation
            #==================================================================================
            
            #==================================================================================
            #region Wait For Job Completion
            #==================================================================================
            
            ##### We Need To Wait For A Job To Finish In Order To Continue With The Loop When
            ##### Running Jobs Are At Or Above The Maximum Allowed Threads Or All Jobs Are Already Created
            ##### And The Time Out Period Has Not Expired
            Write-Debug "Werify Whether The CmdLet Have To Wait A Job To Finish";
            If ((((Get-Job -State "Running" -Verbose:$false -Debug:$false).Count -ge $MaxThreads) -or ($arrCreatedJobs.Count -ge $arrInputObjects.Count)) -and ([System.DateTime]::Now -le $datThreshold))
            {
                Write-Verbose ""; Write-Verbose "";
                Write-Verbose "Wait Until A Job Finish";
                Get-Job -State "Running" -Verbose:$false -Debug:$false | Wait-Job -Any -Timeout 60 -Verbose:$false -Debug:$false | Out-Null;
                Write-Verbose "$([System.DateTime]::Now.ToString(""yyyy-MM-dd HH:mm:ss""))";
            }
            
            #==================================================================================
            #endregion Wait For Job Completion
            #==================================================================================
            
            #==================================================================================
            #region Parallel Jobs Status Verification
            #==================================================================================
            
            ##### Retrieve All Existing Jobs Created On This CmdLet Run
            Write-Debug "Retrieve All Existing Jobs Created On This CmdLet Run";
            [System.Array]$arrCurrentJobs = @();
            [System.Array]$arrCurrentJobs = Get-Job -Verbose:$false -Debug:$false | Where-Object {$arrCreatedJobs.Id -icontains $_.Id} -Verbose:$false -Debug:$false;
            
            ##### Verify Whether There Are Own Current Jobs
            Write-Debug "Verify Whether There Are Own Current Jobs";
            If ($arrCurrentJobs.Count -gt 0)
            {
                ##### Verify Whether The Timeout Period Has Expired
                Write-Verbose "";
                Write-Debug "Verify Whether The Timeout Period Has Expired";
                If ([System.DateTime]::Now -le $datThreshold)
                {
                    ##### Loop Each Job Verification
                    Write-Debug "Loop Each Job Verification";
                    [System.Int32]$intI = 0;
                    For ([System.Int32]$intI = 0; $intI -lt $arrCurrentJobs.Count; $intI++)
                    {
                        ##### Verify The Job Status
                        Write-Debug "Verify The Job Status";
                        Write-Verbose ""; Write-Verbose "$($arrCurrentJobs[$intI].Name) - $($arrCurrentJobs[$intI].State)";
                        Switch -Wildcard ("$($arrCurrentJobs[$intI].State)")
                        {
                            "Running" { Break; }
                            "Stopping" { Start-Job -Name "$($arrCurrentJobs[$intI].Name)"; Break; }
                            "Suspending" { Start-Job -Name "$($arrCurrentJobs[$intI].Name)"; Break; }
                            "Suspended" { Start-Job -Name "$($arrCurrentJobs[$intI].Name)"; Break; }
                            "Stopped" { Start-Job -Name "$($arrCurrentJobs[$intI].Name)"; Break; }
                            "NotStarted" { Start-Job -Name "$($arrCurrentJobs[$intI].Name)"; Break; }
                            
                            default
                            {
                                If ($bolRemoveCompleted)
                                {
                                    ##### All Remaining Job Status Strings Are Either Completed Or Failures
                                    
                                    ##### Get The Job Status And Output
                                    Write-Debug "Get The Job Status And Output";
                                    [SnsPsModule.SnsThreadStatus[]]$arrOutput += Get-SnsJobStatusAndOutput -Job $arrCurrentJobs[$intI] -RemoveJob:$bolRemoveCompleted -Verbose:$bolVerbose -Debug:$false;
                                    [System.Int32]$intCompletedJobs += 1;
                                }
                            }
                        }
                    }
                    
                    ##### Verify Whether The Job Objects Have To Be Preserverd
                    Write-Debug "Verify Whether The Job Objects Have To Be Preserverd";
                    If (-not $bolRemoveCompleted) { [System.Int32]$intCompletedJobs = (@($arrCurrentJobs | Where-Object {(("$($_.State)" -eq "Completed") -or ("$($_.State)" -eq "Failed"))})).Count; }
                }
                ElseIf ($bolRemoveCompleted)
                {
                    ##### Loop Each Job Verification
                    Write-Debug "Loop Each Job Verification";
                    [System.Int32]$intI = 0;
                    For ([System.Int32]$intI = 0; $intI -lt $arrCurrentJobs.Count; $intI++)
                    {
                        ##### Get The Job Status And Output
                        Write-Debug "Get The Job Status And Output";
                        Write-Verbose ""; Write-Verbose "$($arrCurrentJobs[$intI].Name) - $($arrCurrentJobs[$intI].State)";
                        [SnsPsModule.SnsThreadStatus[]]$arrOutput += Get-SnsJobStatusAndOutput -Job $arrCurrentJobs[$intI] -RemoveJob:$bolRemoveCompleted -Verbose:$bolVerbose -Debug:$false;
                        [System.Int32]$intCompletedJobs += 1;
                    }
                }
            }
            
            #==================================================================================
            #endregion Parallel Jobs Status Verification
            #==================================================================================
        }
        While (((Get-SnsIncompleteJobs -CreatedJobs $arrCreatedJobs).Count -gt 0) -or (($arrInputObjects.Count -gt $arrCreatedJobs.Count) -and ([System.DateTime]::Now -le $datThreshold)))
        
        If ($bolProgrBar)
        {
            ##### Close The Progress Bar
            Write-Progress -Activity "Invoke-SnsParallelJobs" -Id 1 -PercentComplete 100 -Verbose:$false -Debug:$false;
            Write-Progress -Activity "Invoke-SnsParallelJobs" -Id 1 -Completed -Verbose:$false -Debug:$false;
        }
        
        #==================================================================================
        #endregion Parallel Jobs Processing
        #==================================================================================
        
        #==================================================================================
        #region Parallel Jobs Output Extraction
        #==================================================================================
        
        ##### Verify Whether The Job Objects Have To Be Preserved
        Write-Debug "Verify Whether The Job Objects Have To Be Preserved";
        If (-not $bolRemoveCompleted)
        {
            ##### Verify Whether There Are Existing Jobs
            Write-Debug "Verify Whether There Are Existing Jobs";
            If ($arrCreatedJobs.Count -gt 0)
            {
                ##### Loop Each Job Verification
                Write-Debug "Loop Each Job Verification";
                [System.Int32]$intI = 0;
                For ([System.Int32]$intI = 0; $intI -lt $arrCreatedJobs.Count; $intI++)
                {
                    ##### Get The Job Status And Output
                    Write-Debug "Get The Job Status And Output";
                    Write-Verbose ""; Write-Verbose "$($arrCreatedJobs[$intI].Name) - $($arrCreatedJobs[$intI].State)";
                    [SnsPsModule.SnsThreadStatus[]]$arrOutput += Get-SnsJobStatusAndOutput -Job $arrCreatedJobs[$intI] -RemoveJob:$bolRemoveCompleted -Verbose:$bolVerbose -Debug:$false;
                    [System.Int32]$intCompletedJobs += 1;
                }
            }
        }
        
        ##### Verify Whether The Count Of Jobs Equals The Count Of Input Objects
        Write-Debug "Verify Whether The Count Of Jobs Equals The Count Of Input Objects";
        If (($arrCreatedJobs.Count) -ne ($arrInputObjects.Count)) { Write-Error "Created Jobs Mismatch" -ErrorAction "Continue"; }
        
        ##### Verify Whether The Count Of Output Objects Equals The Count Of Input Objects
        Write-Debug "Verify Whether The Count Of Output Objects Equals The Count Of Input Objects";
        If (($arrOutput.Count) -ne ($arrInputObjects.Count)) { Write-Error "Output Objects Mismatch" -ErrorAction "Continue"; }
        
        ##### Pass The Output Object To The Pipeline
        Write-Debug "Pass Output Object To The Pipeline";
        $PSCmdlet.WriteObject($arrOutput);
        
        #==================================================================================
        #endregion Parallel Jobs Output Extraction
        #==================================================================================
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrOutput";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrCurrentJobs";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrArgs";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplatting";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrCreatedJobs";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intCompletedJobs";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "datThreshold";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolProgrBar";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrInputObjects";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objIniScrBlock";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objScrBlock";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolRemoveCompleted";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolVerbose";
        
        $PSCmdlet.MyInvocation.BoundParameters | Select-Object -ExpandProperty "Keys" | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        ##### Collect The Garbage
        [System.GC]::Collect();
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose ""; Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!"; Write-Verbose "";
    }
}

##### New-SnsTemporaryPsDrive =====================================================
Function New-SnsTemporaryPsDrive ()
{
<#
.SYNOPSIS
This CmdLet Maps A Network Folder To Temporary PsDrive Available In The PowerShell Runspace Only.
.DESCRIPTION
This CmdLet Maps A Network Folder To Temporary PsDrive Available In The PowerShell Runspace Only.
The CmdLet Is Capable To Map The Network Location With Authentication. The CmdLet Have Five Parameter Set
Corresponding To Authentication Methods:
-- Kerberos Here The CmdLet Use The Windows Integrated Authentication With Kerberos In This Scenario Is Created A
Remote Session Within The Security Context Of The Currently Logged On User. Impersonalizing Is Not Possible In
This Scenario.
-- Interactive In This Parameter Set The CmdLet Opens A Window Where The User Can Specify His Credentials. It
Cannot Be Used Whenever The CmdLet Is Executed In As Service Mode. Whenever The Script Calling The CmdLet Is Run
As A Service On A Scheduled Task There Is No Real Person To Specify The Credentials.
-- FolderPath Here Have To Be Specified The UserName And The Full Absolute UNC Folder Path Where The Encrypted
Credential File Resides.
-- FilePath Here Have To Be Provided The Full Absolute UNC Path To The Credential File. The CmdLet Will Try To
Enumerate The UserName From The FileName.
-- Credential Here Have To Be Provided System.Management.Automation.PSCredential Object.
Whenever Windows Already Have Session To The Specified Network Location, Will Be Used Kerberos ParameterSet,
Because Windows Does Not Allow Connection To A Single Resource With Two Different Security Contexts.
In Case The Session Creation Fail The CmdLet Is Capable To 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. Throwing Of Terminating Error Normally Keeps The PowerShell Process Running Which Will Prevent The Next
Script Scheduled Instances From Execution. Additionally Any Possible Script Monitoring Could Be Cheated That The
Monitored Script Is Still Running Because Its Process Is Running.
.PARAMETER NetworkShare
Specifies The Full Absolute UNC Path To The Network Location Which Have To Be Mapped To The PSSession As
PsDrive.
Parameter Set: All
Parameter Alias: Folder, Path
Parameter Validation: Yes, Using Syntax Validation.
.PARAMETER Name
Specifies The PsDriveName Name.
If Omitted The CmdLet Will Generate It Automatically Using The NetworkShare Parameter.
Parameter Set: All
Parameter Alias: PsDriveName
Parameter Validation: N/A
.PARAMETER UserName
Specifies The UserName Of The Account To Authenticate Against The Network Location.
Parameter Set: FolderPath
Parameter Alias: N/A
Parameter Validation: N/A
.PARAMETER FolderPath
Specifies The Full Absolute UNC Folder Path Where The Credential File For The Account Specified In UserName
Parameter Resides.
Parameter Set: FolderPath
Parameter Alias: CredentialFolder
Parameter Validation: N/A
.PARAMETER FilePath
Specifies The Full Absolute UNC Path To The Credential File For The Account Which Will Be Used To Authenticate
Against The Resource.
Parameter Set: FilePath
Parameter Alias: CredentialFile
Parameter Validation: N/A
.PARAMETER Credential
Specifies [System.Management.Automation.PSCredential] Object For The Account Which Will Be Used To
Authenticate Against The Resource.
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 AuthenticationKerberos
Specifies Whether The Currently Logged On User Security Context Shall Be Used To Authenticate Against The
Resource.
Parameter Set: Kerberos
Parameter Alias: Kerberos
Parameter Validation: N/A
.PARAMETER Attempts
Specifies The Number Of Attempts That Have To Be Made To Map The PsDrive.
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 To Revert The PsDriveName When The Mapping Is Successful.
Parameter Set: All
Parameter Alias: N/A
Parameter Validation: N/A
.INPUTS
Pipeline Input For NetworkShare Parameter. And Pipeline By Property Name For NetworkShare, Name, UserName,
FolderPath, FilePath, Credential, Interactive And AuthenticationKerberos Parameters.
.OUTPUTS
[System.String[]] Which The PsDriveName Of The Mapped Drive.
.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[]]$arrDrives = New-SnsTemporaryPsDrive -AuthenticationKerberos;
.EXAMPLE
[System.String[]]$arrDrives = New-SnsTemporaryPsDrive -Interactive;
.EXAMPLE
[System.String[]]$arrDrives = New-SnsTemporaryPsDrive -UserName "john.smith@contoso.com" -FolderPath "C:\";
.EXAMPLE
[System.String[]]$arrDrives = New-SnsTemporaryPsDrive -FilePath "C:\john.smith@contoso.com.ini";
.EXAMPLE
[System.String[]]$arrDrives = New-SnsTemporaryPsDrive -Credential $objCredential;
.LINK
svesavov / SnsPsModule - https://github.com/svesavov/SnsPsModule
.LINK
Svetoslav Savov on LinkedIn - https://www.linkedin.com/in/svetoslavsavov
#>

[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "Kerberos")]
Param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias("Folder", "Path")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)".EndsWith("\"))})]
    [ValidateScript({((-not "$($_)".StartsWith("\\")) -or ("$($_.Split(""\"")[2])" -eq "$($_.Split(""\"")[2].Replace("" "", """").Replace(""`t"", """"))"))})]
    [ValidateScript({((-not "$($_)".StartsWith("\\")) -or ("$($_.Split('\')[2])" -match '\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\b)){4}\b') -or ("$($_.Split('\')[2])" -match '^[a-zA-Z0-9-]+\.[a-zA-Z.]{2,25}$'))})]
    [ValidateNotNullOrEmpty()][System.String]$NetworkShare,
    
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("PsDriveName")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({("$($_)" -eq "$($_.Replace("" "", """").Replace(""`t"", """"))")})]
    [ValidateScript({((Get-PSDrive -PSProvider "FileSystem" -Verbose:$false -Debug:$false).Name -inotcontains "$($_)")})]
    [ValidateNotNullOrEmpty()][System.String]$Name,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateNotNullOrEmpty()][System.String]$UserName,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FolderPath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("CredentialFolder")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({([System.IO.Directory]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FolderPath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "FilePath", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("CredentialFile")]
    [ValidateScript({("$($_)" -eq "$($_.Trim())")})]
    [ValidateScript({([System.IO.File]::Exists("$($_)"))})]
    [ValidateNotNullOrEmpty()][System.String]$FilePath,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Credential", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [ValidateNotNullOrEmpty()][System.Management.Automation.PSCredential]$Credential,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Interactive", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Switch]$Interactive = $false,
    
    [Parameter(Mandatory = $false, ParameterSetName = "Kerberos", ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [Alias("Kerberos")]
    [Switch]$AuthenticationKerberos = $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 "New-SnsTemporaryPsDrive";
        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.String]$strPsDrive = "";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Int32]$intI = 0;
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
    }
    
    ##### Override The Process Method
    Process
    {
        Write-Debug "Override Process Method";
        Write-Verbose "";
        
        ##### Initialize The Variables
        [System.String]$strPsDrive = "";
        [System.Management.Automation.PSCredential]$objCredential = $null;
        [System.Int32]$intI = 0;
        [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
        
        #==================================================================================
        #region Check Whether The PsDrive Not Exists Yet
        #==================================================================================
        
        ##### Enumerate The PsDrives With The Specified Root
        Write-Verbose "Enumerate The PsDrives With The Specified Root";
        [System.String]$strPsDrive = "";
        [System.String]$strPsDrive = "$(Get-PSDrive -PSProvider ""FileSystem"" -ErrorAction ""SilentlyContinue"" -WarningAction ""SilentlyContinue"" -Verbose:$false -Debug:$false | `
            Where-Object {""$($_.Root)"".TrimEnd(""\"") -eq ""$($NetworkShare)"".TrimEnd(""\"")} -Verbose:$false -Debug:$false | `
            Select-Object -ExpandProperty ""Name"" -Verbose:$false -Debug:$false)"
;
        #####
        
        ##### Verify The Output
        Write-Debug "Verify The Output";
        If (-not -not "$($strPsDrive)")
        {
            [System.String]$strEventMessage = "PsDrive ""$($NetworkShare)"" Is Already Mapped As ""$($strPsDrive)"".";
            Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Information" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
            Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
        }
        
        #==================================================================================
        #endregion Check Whether The PsDrive Not Exists Yet
        #==================================================================================
        
        #==================================================================================
        #region Enumerate The Drive Name
        #==================================================================================
        
        ##### Continue If The PsDrive Not Mapped Yet
        Write-Debug "Continue If The PsDrive Not Mapped Yet";
        If (-not "$($strPsDrive)")
        {
            ##### Verify Whether Name Is Not Provided
            Write-Debug "Verify Whether Name Is Not Provided";
            If (-not "$($Name)")
            {
                ##### Enumerate The PsDrive Name
                Write-Debug "Enumerate The PsDrive Name";
                [System.String]$Name = "";
                [System.String]$Name = "$($NetworkShare.Split(""\"")[-2].Replace("" "", """"))";
                Write-Verbose "Enumerated PsDrive Name: ""$($Name)"".";
            }
            
            ##### Verify Whether The PsDriveName Is Not Already In Use
            Write-Debug "Verify The Target Server Generation";
            If ((Get-PSDrive -PSProvider "FileSystem" -Verbose:$false -Debug:$false | Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false) -icontains "$($Name)")
            {
                [System.String]$strEventMessage = "The PsDrive Name Is Already In Use With Different Root.";
                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 Drive Name
        #==================================================================================
        
        #==================================================================================
        #region Initialize The Credentials Object
        #==================================================================================
        
        ##### Continue If The PsDrive Not Mapped Yet
        Write-Debug "Continue If The PsDrive Not Mapped Yet";
        If (-not "$($strPsDrive)")
        {
            ###### Verify The Parameter Set Name
            Write-Debug "Verify The Parameter Set Name";
            Switch ("$($PSCmdlet.ParameterSetName)")
            {
                "FolderPath"
                {
                    ##### Generate The Credential Object In FolderPath Parameter Set
                    Write-Verbose "Generate The Credential Object In FolderPath Parameter Set";
                    [System.Management.Automation.PSCredential]$objCredential = $null;
                    [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -UserName "$($UserName)" -FolderPath "$($FolderPath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                    Break;
                }
                
                "FilePath"
                {
                    ##### Generate The Credential Object In FilePath Parameter Set
                    Write-Verbose "Generate The Credential Object In FilePath Parameter Set";
                    [System.Management.Automation.PSCredential]$objCredential = $null;
                    [System.Management.Automation.PSCredential]$objCredential = Import-SnsCredentialFile -FilePath "$($FilePath)" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                    Break;
                }
                
                "Credential"
                {
                    ##### Assign The Provided Credential Object In Credential Parameter Set
                    Write-Verbose "Assign The Provided Credential Object In Credential Parameter Set";
                    [System.Management.Automation.PSCredential]$objCredential = $null;
                    [System.Management.Automation.PSCredential]$objCredential = $Credential;
                    Break;
                }
                
                Default
                {
                    ##### Do Nothing
                }
            }
            
            ##### Verify If It Is Interactive Session Kerberos Wont Be Used And There Are No Credentials
            Write-Debug "Verify If It Is Interactive Session Kerberos Wont Be Used And There Are No Credentials";
            If (("$($PSCmdlet.ParameterSetName)" -ne "Kerberos") -and (-not "$($objCredential.UserName)") -and [System.Environment]::UserInteractive)
            {
                ##### Loop Interactive Credentials Dialog With The User
                Write-Debug "Loop Interactive Credentials Dialog With The User";
                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;
                }
                While ((-not "$($objCredential.UserName)") -or (-not "$($objCredential.GetNetworkCredential().Password)"))
            }
            
            ##### Verify The Credentials Object
            Write-Debug "Verify The Credentials Object";
            If (("$($PSCmdlet.ParameterSetName)" -ne "Kerberos") -and (-not "$($objCredential.UserName)"))
            {
                [System.String]$strEventMessage = "Failed To Enumerate The Credential Object For PsDrive Mapping.";
                Log-SnsEventLogMessageHelper -Message "$($strEventMessage)" -EventLogEntryType "Error" -EventSource "$($EventSource)" -Verbose:$false -Debug:$false;
                Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strEventMessage";
                [System.Management.Automation.PSCredential]$objCredential = $null;
                Return;
            }
        }
        
        #==================================================================================
        #endregion Initialize The Credentials Object
        #==================================================================================
        
        #==================================================================================
        #region Map The PsDrive
        #==================================================================================
        
        ##### Continue If The PsDrive Not Mapped Yet
        Write-Debug "Continue If The PsDrive Not Mapped Yet";
        If (-not "$($strPsDrive)")
        {
            ##### Loop The PsDrive Creation
            Write-Debug "Loop The PsDrive Creation";
            [System.Int32]$intI = 0;
            Do
            {
                ##### Generate The Splatting Hashtable
                Write-Debug "Generate The Splatting Hashtable";
                [System.Collections.Specialized.OrderedDictionary]$hshSplat = [Ordered]@{};
                $hshSplat.Add("Name", "$($Name)");
                $hshSplat.Add("Root", "$($NetworkShare)".TrimEnd("\"));
                $hshSplat.Add("Scope", "Global");
                $hshSplat.Add("PSProvider", "FileSystem");
                $hshSplat.Add("ErrorAction", "Continue");
                $hshSplat.Add("WarningAction", "Continue");
                $hshSplat.Add("Verbose", $false);
                $hshSplat.Add("Debug", $false);
                $hshSplat.Add("WhatIf", $false);
                $hshSplat.Add("Confirm", $false);
                
                ##### Check About Existing Connections
                ##### If The User Have A Connection To The Share
                ##### The Windows Will Not Allow Same connection With 2 Different Users
                Write-Verbose "Check About Existing Connections";
                If ( `
                    ("$($PSCmdlet.ParameterSetName)" -notlike "Kerberos") `
                    -and `
                    (-not -not "$($objCredential.UserName)") `
                    -and `
                    (-not -not "$($objCredential.GetNetworkCredential().Password)") `
                    -and `
                    ((Get-ChildItem -Path "$($NetworkShare)" -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue").Count -eq 0) `
                )
                {
                    Write-Verbose "Using $($objCredential.UserName)'s Credential";
                    $hshSplat.Add("Credential", $objCredential);
                }
                
                ##### Map The File Share To The PSSession
                Write-Verbose "Map The File Share To The PSSession As ""$($Name)""";
                [System.String]$strPsDrive = "";
                [System.String]$strPsDrive = New-PSDrive @hshSplat | Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false -ErrorAction "SilentlyContinue" -WarningAction "SilentlyContinue";
                
                ##### Process The Loop Variable And TimeOut
                If (-not "$($strPsDrive)") { Start-Sleep -Seconds 2 -Verbose:$false -Debug:$false; }
                [System.Int32]$intI = $intI + 1;
            }
            While ((-not "$($strPsDrive)") -and ($intI -lt $Attempts))
            
            ##### Verify The File Share Mapping
            Write-Debug "Verify The File Share Mapping";
            If (-not "$($strPsDrive)")
            {
                [System.String]$strEventMessage = "Failed Connect To: ""$($NetworkShare)""";
                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 Map The PsDrive
        #==================================================================================
        
        #==================================================================================
        #region Send The Output Object To The Pipeline
        #==================================================================================
        
        ##### Verify Whether The File Share Is Mapped To The PSSession
        Write-Debug "Verify Whether The File Share Is Mapped To The PSSession";
        If ((-not -not "$($strPsDrive)") -and ($PassThru.IsPresent))
        {
            ##### Pass The Output Object To The Pipeline
            Write-Debug "Pass Output Object To The Pipeline";
            $PSCmdlet.WriteObject("$($strPsDrive):\");
        }
        
        #==================================================================================
        #endregion Send The Output Object To The Pipeline
        #==================================================================================
    }
    
    ##### Override The End Method
    End
    {
        Write-Debug "Override End Method";
        Write-Verbose "";
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "hshSplat";
        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 "strPsDrive";
        
        $PSCmdlet.MyInvocation.BoundParameters.Keys | ForEach { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
        
        ##### Stop The StopWatch
        $objCmdStopWatch.Stop();
        Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        Write-Verbose "End!";
    }
}

##### Update-SnsNotepadPlusPlusLanguageFile =======================================
Function Update-SnsNotepadPlusPlusLanguageFile ()
{
<#
.SYNOPSIS
This CmdLet Updates The Notepad++ Language File Amending The Existing PowerShell CmdLets, Functions And Aliases
With Automatically Enumerated Ones.
.DESCRIPTION
This CmdLet Updates The Notepad++ Language File Amending The Existing PowerShell CmdLets, Functions And Aliases
With Automatically Enumerated Ones.
 
The CmdLet Enumerates All PowerShell Build In CmdLets, Functions And Aliases, And All CmdLets, Functions And
Aliases In The Installed PowerShell Modules In The Default PowerShell Modules Locations.
The Default PowerShell Modules Locations Are:
-- C:\Users\<username>\Documents\WindowsPowerShell\Modules; - Designated To Keep All PowerShell Modules Installed
With Scope "CurrentUser".
-- C:\Program Files\WindowsPowerShell\Modules; - Designated To Keep All PowerShell Modules Installed With Scope
"AllUsers".
-- C:\Windows\system32\WindowsPowerShell\v1.0\Modules; - Reserved For Microsoft Use.
The PowerShell Modules Locations Can Be Modified By The Users. In Order To Identify The Actual Ones On A Specific
Machine Check $env:PSModulePath Automatic Variable.
 
The CmdLet Will Check Whether It Is Run On A Domain Joined Machine In An Elevated Session, Then It Will Offer The
User To Make Remote Session To An Exchange Server. If You Make The Session, The Exchange Server Functions And
Aliases Will Be Extracted From The Remote Session And Will Be Added To The Enumerated PowerShell CmdLets. The
Extracted Exchange Server Functions And Aliases Depend On The RBAC Role Of The Account Which Connected To The
Exchange, The Commands Not Allowed For The Users RBAC Role Will Be Not Present Among The Extracted Functions And
Aliases.
The CmdLet Will Check Whether It Is Run In An Elevated Session, Then Will Offer The Users To Make Remote Session
To Exchange Online. If You Make The Session, The Exchange Online Functions And Aliases Will Be Extracted From The
Remote Session And Will Be Added To The Enumerated PowerShell CmdLets. The Extracted Exchange Online Functions And
Aliases Depend On The RBAC Role Of The Account Which Connected To The Exchange, The Commands Not Allowed For The
Users RBAC Role Will Be Not Present Among The Extracted Functions And Aliases.
The Last Two Actions Are Made Only If The CmdLet Is Executed Interactively.
 
In Case Where New CmdLets, Functions And Aliases To Be Added To The Notepad++ Language File, The Old Files Is
Renamed Via Adding Prefix In The File Name With Digits Representing The Current Date And Time With Syntax
"yyyyMMddHHmmss_" And Adding Suffix "_bkp" To The File Extension.
.PARAMETER Path
Specifies The Full Absolute UNC Path To Notepad++ Language File.
 
If Omitted The CmdLet Will Automatically Enumerate The Notepad++ Language File Located In The Current User
"AppData" Folder.
 
It Is Not Recommended To Modify The Notepad++ Language Model File As It Affects All The Users On The Machine
And When Manually Modified Might Lead To Corruption In Notepad++ Language Models.
 
Parameter Alias: N/A
Parameter Validation: Yes, Using File Existence Validation.
.PARAMETER Keep
Specifies To The CmdLet To Keep The Previous CmdLets, Functions And Aliases.
 
Normally It Should Not Affect The Users Because The Default CmdLets, Functions And Aliases Should Not Be
Removed, Therefore The CmdLet Should Enumerate Them And They Will Be Present Among The Enumerated Ones.
 
Can Be Quite Useful In Case A Non-Default PowerShell Module Is Uninstalled, And You Want Its CmdLets,
Functions And Aliases Removed From Notepad++ Language File.
 
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
Update-SnsNotepadPlusPlusLanguageFile -Path $Path -Keep:$false;
.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 = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [ValidateNotNullOrEmpty()][ValidateScript( { [System.IO.File]::Exists("$($_)") } )][System.String]$Path = "",
    [Parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [Switch]$Keep = $true
)
    ##### Override The Begin Method
    Begin
    {
        Write-Debug "Override Begin Method";
        Write-Verbose ""; Write-Verbose "Update-SnsNotepadPlusPlusLanguageFile"; Write-Verbose "";
        
        ##### Initialize New Measure Watch
        [System.Diagnostics.Stopwatch]$objCmdStopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        
        ##### 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
        If (-not "$($Path)") { [System.String]$Path = "$([System.Environment]::GetFolderPath(""ApplicationData""))\Notepad++\langs.xml"; }
        [System.String[]]$arrExcludeFunctions = @( "$($PSCmdlet.MyInvocation.MyCommand.Name)", "Add-SnsSecurityPolicy" );
        [System.String[]]$arrLanges = @();
        [System.Int32]$intPSIndex = 0;
        [System.Array]$arrCommands = @();
        [System.String]$strTmp = "";
        [System.String[]]$arrTmp = @();
        [System.Int32]$intI = 0;
        [System.Int32]$intLineIndex = 0;
        [System.String]$strCmdLets = "";
        [System.String]$strNewLine = "";
        [System.Boolean]$bolExportNeeded = $false;
        [System.String]$strAliases = "";
        [System.String]$strBackupFile = "";
        [System.String[]]$arrExport = @();
        [System.Object[]]$arrCompare = @();
        
        #==================================================================================
        #region Import The Specified Language File
        #==================================================================================
        
        ##### Verify Notepad++ Language File Existence
        Write-Verbose "Verify Notepad++ Language File Existence";
        [System.String[]]$arrLanges = @();
        If ([System.IO.File]::Exists("$($Path)"))
        {
            ##### Import The Specified Language File
            Write-Verbose "Import Notepad++ Language File";
            [System.String[]]$arrLanges = Get-Content -Path "$($Path)" -Encoding "Ascii" -Force:$true -Verbose:$false -Debug:$false;
        }
        
        ##### Verify Notepad++ Language File Import
        Write-Debug "Verify Notepad++ Language File Import";
        If ($arrLanges.Count -le 0)
        {
            Write-Error "Failed To Import File: ""$($Path)""" -ErrorAction "Continue";
            Return;
        }
        
        #==================================================================================
        #endregion Import The Specified Language File
        #==================================================================================
        
        #==================================================================================
        #region Enumerate The PowerShell Language Begin
        #==================================================================================
        
        ##### Continue If Notepad++ Language File Is Imported
        Write-Debug "Continue If Notepad++ Language File Is Imported";
        [System.Int32]$intPSIndex = 0;
        If ($arrLanges.Count -gt 0)
        {
            ##### Enumerate PowerShell Language Section Begin Within Notepad++ Language File
            Write-Verbose "Enumerate PowerShell Language Section Begin Within Notepad++ Language File";
            For ([System.Int32]$intPSIndex = 0; $intPSIndex -lt $arrLanges.Count; $intPSIndex++)
            {
                ##### Verify Whether The Current Line Is PowerShell Language Opening Tag
                Write-Debug "Verify Whether The Current Line Is PowerShell Language Opening Tag";
                If ("$($arrLanges[$intPSIndex])" -like "*<Language name=""powershell"" ext=""ps1 psm1*"" commentLine=""#"" commentStart=""&lt;#"" commentEnd=""#&gt;"">") { Break; }
            }
        }
        
        ##### Verify Whether PowerShell Language Section Begin Within Notepad++ Language File Is Enumerated
        Write-Debug "Verify Whether PowerShell Language Section Begin Within Notepad++ Language File Is Enumerated";
        If (($intPSIndex -le 0) -or ($intPSIndex -ge $arrLanges.Count))
        {
            Write-Error "Failed To Enumerate PowerShell Language Section In File: ""$($Path)""" -ErrorAction "Continue";
            Return;
        }
        
        #==================================================================================
        #endregion Enumerate The PowerShell Language Begin
        #==================================================================================
        
        #==================================================================================
        #region Enumerate All PowerShell Commands Available On The Current Machine
        #==================================================================================
        
        ##### Enumerate The PowerShell Commands Both Build In And From All Locally Available PowerShell Modules
        Write-Verbose "Enumerate The PowerShell Commands Both Build In And From All Locally Available PowerShell Modules";
        [System.Array]$arrCommands = @();
        [System.Array]$arrCommands = Get-Command -All:$true -Verbose:$false -Debug:$false;
        
        ##### Verify The PowerShell Commands Enumeration
        Write-Debug "Verify The PowerShell Commands Enumeration";
        If ($arrCommands.Count -le 0)
        {
            Write-Error "Failed To Enumerate The PowerShell Commands" -ErrorAction "Continue";
            Return;
        }
        
        ##### Verify Whether It Is Interactive Session And SnsPsModule Is Already Installed
        Write-Verbose "Verify Whether It Is Interactive Session And ""SnsPsModule"" Is Already Installed";
        If ([System.Environment]::UserInteractive -and (-not -not (Get-Module -Name "SnsPsModule" -ListAvailable:$true -Verbose:$false -Debug:$false)))
        {
            ##### Verify Whether SnsPsModule Is Not Imported Already
            Write-Debug "Verify Whether ""SnsPsModule"" Is Not Imported Already";
            If (-not (Get-Module -Name "SnsPsModule" -Verbose:$false -Debug:$false))
            {
                ##### Import Module "SnsPsModule"
                Write-Verbose "Import Module ""SnsPsModule""";
                Import-Module -Name "SnsPsModule" -Verbose:$false -Debug:$false;
            }
            
            ##### Load System.Windows.Forms Assembly
            Write-Verbose "Load ""System.Windows.Forms"" Assembly";
            [Void][Reflection.Assembly]::Load( "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" );
            
            ##### Verify Whether "SnsPsModule" Is Loaded, The PSSession Is In Elevated Mode And The Machine Is Connected To Active Directory
            Write-Verbose "Verify Whether ""SnsPsModule"" Is Loaded, The PSSession Is In Elevated Mode And The Machine Is Connected To Active Directory";
            [System.String]$strTmp = "Would You Like To Connect To An Exchange Server In Order To Add The Exchange CmdLets In The Notepad++ Language File?";
            If ( `
                (-not -not (Get-Module -Name "SnsPsModule" -Verbose:$false -Debug:$false)) `
                -and `
                ((New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) `
                -and `
                (-not -not "$(([ADSI]"""").distinguishedName)") `
                -and `
                ("$([System.Windows.Forms.MessageBox]::Show($strTmp, ""Information"", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Information))" -eq "Yes") `
            )
            {
                ##### Enumerate The Currently Imported PowerShell Modules
                Write-Debug "Enumerate The Currently Imported PowerShell Modules";
                [System.String[]]$arrTmp = @();
                [System.String[]]$arrTmp = Get-Module -Verbose:$false -Debug:$false | Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false;
                
                ##### Connect To A Random Exchange Server
                Write-Verbose "Connect To A Random Exchange Server";
                Connect-SnsExchangeOnPremises -Verbose:$false -Debug:$false | Out-Null;
                
                ##### The Remote Exchange Server Connection Have To Create A Temp Module / Modules Which Were Not Present Before The Connection
                ##### Need To Enumerate That New Temp Module To Get The Exchange Server CmdLets From It
                ##### Enumerate The Newly Imported PowerShell Module
                Write-Verbose "Enumerate The Newly Imported PowerShell Module";
                [System.String[]]$arrTmp = Get-Module -Verbose:$false -Debug:$false | `
                    Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false | `
                    Where-Object { $arrTmp -inotcontains "$($_)" } -Verbose:$false -Debug:$false | `
                    Select-Object -Unique:$true -Verbose:$false -Debug:$false | Sort-Object -Verbose:$false -Debug:$false;
                #####
                
                ##### Verify Whether Newly Imported PowerShell Modules Are Enumerated
                ##### The Connection Might Fail And No New Modules To Be Imported
                Write-Debug "Verify Whether Newly Imported PowerShell Modules Are Enumerated";
                If ($arrTmp.Count -gt 0)
                {
                    ##### Loop Each Newly Imported PowerShell Module
                    Write-Debug "Loop Each Newly Imported PowerShell Module";
                    [System.Int32]$intI = 0;
                    For ([System.Int32]$intI = 0; $intI -lt $arrTmp.Count; $intI++)
                    {
                        ##### Enumerate The Commands Within The Current PowerShell Module
                        Write-Verbose "Enumerate The Commands Within ""$($arrTmp[$intI])"" PowerShell Module";
                        [System.Array]$arrCommands += Get-Command -Module "$($arrTmp[$intI])" -Verbose:$false -Debug:$false;
                    }
                }
            }
            
            ##### Verify Whether "SnsPsModule" Is Loaded And The PSSession Is In Elevated Mode
            Write-Verbose "Verify Whether ""SnsPsModule"" Is Loaded And The PSSession Is In Elevated Mode";
            [System.String]$strTmp = "Would You Like To Connect To Exchange Online In Order To Add The Exchange Online CmdLets In The Notepad++ Language File?";
            If ( `
                (-not -not (Get-Module -Name "SnsPsModule" -Verbose:$false -Debug:$false)) `
                -and `
                ((New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) `
                -and `
                ("$([System.Windows.Forms.MessageBox]::Show($strTmp, ""Information"", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Information))" -eq "Yes") `
            )
            {
                ##### Enumerate The Currently Imported PowerShell Modules
                Write-Debug "Enumerate The Currently Imported PowerShell Modules";
                [System.String[]]$arrTmp = @();
                [System.String[]]$arrTmp = Get-Module -Verbose:$false -Debug:$false | Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false;
                
                ##### Connect To The On Premises Exchange
                Write-Verbose "Connect To The Exchange Online";
                Connect-SnsExchangeOnline -Verbose:$false -Debug:$false | Out-Null;
                
                ##### The Remote Exchange Online Connection Have To Create A Temp Module / Modules Which Were Not Present Before The Connection
                ##### Need To Enumerate That New Temp Module To Get The Exchange Online CmdLets From It
                ##### Enumerate The Newly Imported PowerShell Module
                Write-Verbose "Enumerate The Newly Imported PowerShell Module";
                [System.String[]]$arrTmp = Get-Module -Verbose:$false -Debug:$false | `
                    Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false | `
                    Where-Object { $arrTmp -inotcontains "$($_)" } -Verbose:$false -Debug:$false | `
                    Select-Object -Unique:$true -Verbose:$false -Debug:$false | Sort-Object -Verbose:$false -Debug:$false;
                #####
                
                ##### Verify Whether Newly Imported PowerShell Modules Are Enumerated
                ##### The Connection Might Fail And No New Modules To Be Imported
                Write-Debug "Verify Whether Newly Imported PowerShell Modules Are Enumerated";
                If ($arrTmp.Count -gt 0)
                {
                    ##### Loop Each Newly Imported PowerShell Module
                    Write-Debug "Loop Each Newly Imported PowerShell Module";
                    [System.Int32]$intI = 0;
                    For ([System.Int32]$intI = 0; $intI -lt $arrTmp.Count; $intI++)
                    {
                        ##### Enumerate The Commands Within The Current PowerShell Module
                        Write-Verbose "Enumerate The Commands Within ""$($arrTmp[$intI])"" PowerShell Module";
                        [System.Array]$arrCommands += Get-Command -Module "$($arrTmp[$intI])" -Verbose:$false -Debug:$false;
                    }
                }
            }
        }
        
        #==================================================================================
        #endregion Enumerate All PowerShell Commands Available On The Current Machine
        #==================================================================================
        
        #==================================================================================
        #region Enumerate And Update The PowerShell CmdLets
        #==================================================================================
        
        ##### Continue If PowerShell Language Section Begin Is Enumerated
        Write-Debug "Continue If PowerShell Language Section Begin Is Enumerated";
        [System.Int32]$intLineIndex = 0;
        If (($intPSIndex -gt 0) -and ($intPSIndex -lt $arrLanges.Count))
        {
            ##### Enumerate PowerShell Commands Line
            Write-Debug "Enumerate PowerShell Commands Line";
            For ([System.Int32]$intLineIndex = $intPSIndex; ($intLineIndex -lt $arrLanges.Count) -and ($intLineIndex -lt ($intPSIndex + 5)); $intLineIndex++)
            {
                ##### Verify Whether The Current Line Is PowerShell Commands Line
                Write-Debug "Verify Whether The Current Line Is PowerShell Commands Line";
                If ($arrLanges[$intLineIndex].Contains("<Keywords name=""instre2"">")) { Break; }
            }
        }
        
        ##### Verify Whether PowerShell Commands Line Is Enumerated
        Write-Debug "Verify Whether PowerShell Commands Line Is Enumerated";
        If (($intLineIndex -le 0) -or ($intLineIndex -ge $arrLanges.Count) -or ($intLineIndex -le $intPSIndex) -or ($intLineIndex -ge ($intPSIndex + 5)))
        {
            Write-Error "Failed To Enumerate The PowerShell Commands Section Within File: ""$($Path)""" -ErrorAction "Continue";
            Return;
        }
        
        ##### Continue If PowerShell Commands Line Is Enumerated
        Write-Debug "Continue If PowerShell Commands Line Is Enumerated";
        [System.String]$strCmdLets = "";
        If (($intLineIndex -gt 0) -and ($intLineIndex -lt $arrLanges.Count) -and ($intLineIndex -gt $intPSIndex) -and ($intLineIndex -lt ($intPSIndex + 5)))
        {
            ##### Verify Whether The Existing Commands Have To Be Kept
            Write-Debug "Verify Whether The Existing Commands Have To Be Kept";
            [System.String]$strTmp = "";
            If ($Keep.IsPresent)
            {
                ##### Enumerate The Existing Commands Within Notepad++ Language File
                Write-Verbose "Enumerate The Existing Commands Within Notepad++ Language File";
                [System.String]$strTmp = $arrLanges[$intLineIndex].Replace("<Keywords name=""instre2"">", "").Replace("</Keywords>", "").Trim();
            }
            
            ##### Verify The Existing Commands Enumeration
            ##### No Exit If No Any Commands Either Notepad++ Might Come Without Commands Or The User Might Want To Not Keep Them
            Write-Debug "Verify The Existing Commands Enumeration";
            [System.String[]]$arrTmp = @();
            If (-not -not "$($strTmp)")
            {
                ##### Split The Notepad++ Commands To Array
                Write-Debug "Split The Notepad++ Commands To Array";
                [System.String[]]$arrTmp += $strTmp.Split(@(" "), [System.StringSplitOptions]::RemoveEmptyEntries);
            }
            
            ##### Filter The Enumerated PowerShell Commands Available On The Machine To Cmdlets And Functions
            Write-Verbose "Filter The Enumerated PowerShell Commands Available On The Machine To Cmdlets And Functions";
            [System.String[]]$arrTmp += $arrCommands | Where-Object { ("$($_.CommandType)" -eq "Cmdlet") -or ("$($_.CommandType)" -eq "Function") } -Verbose:$false -Debug:$false | `
                Select-Object -Property @{ "n" = "Name"; "e" = { "$($_.Name)".ToLower() }; } -Verbose:$false -Debug:$false | `
                Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false;
            #####
            
            ##### Verify Whether There Are Functions Which Should Not Be Included In The Notepad++ Language File
            Write-Debug "Verify Whether There Are Functions Which Should Not Be Included In The Notepad++ Language File";
            If ($arrExcludeFunctions.Count -gt 0)
            {
                ##### Loop All The Specified Excluded Functions
                Write-Debug "Loop All The Specified Excluded Functions";
                [System.Int32]$intI = 0;
                For ([System.Int32]$intI = 0; $intI -lt $arrExcludeFunctions.Count; $intI++)
                {
                    ##### Filter Out The Functions In This Script To Be Not Included In The Language File
                    Write-Verbose "Exclude Function ""$($arrExcludeFunctions[$intI])"" From The Notepad++ Language File";
                    [System.String[]]$arrTmp = $arrTmp | Where-Object { "$($_.ToLower())" -ne "$($arrExcludeFunctions[$intI].ToLower())" } -Verbose:$false -Debug:$false;
                }
            }
            
            ##### Filter Out The Entries Without Dash And The Repeatable Entries And Sort The Array
            Write-Debug "Filter Out The Entries Without Dash And The Repeatable Entries And Sort The Array";
            [System.String[]]$arrTmp = $arrTmp | Where-Object { "$($_)".Contains("-") -and (-not "$($_)".ToLower().EndsWith("helper"))} -Verbose:$false -Debug:$false | `
                Select-Object -Unique:$true -Verbose:$false -Debug:$false | Sort-Object -Verbose:$false -Debug:$false;
            #####
            
            ##### Verify Whether There Are Commands For Including Into The Language File
            Write-Debug "Verify Whether There Are Commands For Including Into The Language File";
            If ($arrTmp.Count -gt 0)
            {
                ##### Join The Commands Array To A String Recognizable By Notepad++
                Write-Debug "Join The Commands Array To A String Recognizable By Notepad++";
                [System.String]$strCmdLets = [System.String]::Join(" ", $arrTmp).ToLower();
            }
        }
        
        ##### Continue If PowerShell Commands Line Is Enumerated
        Write-Debug "Continue If PowerShell Commands Line Is Enumerated";
        If (($intLineIndex -gt 0) -and ($intLineIndex -lt $arrLanges.Count) -and ($intLineIndex -gt $intPSIndex) -and ($intLineIndex -lt ($intPSIndex + 5)))
        {
            ##### Initialize The New PowerShell Commands Line
            Write-Debug "Initialize The New PowerShell Commands Line";
            [System.String]$strNewLine = "";
            [System.String]$strNewLine = "$($arrLanges[$intLineIndex])" -Replace ("<Keywords name=""instre2"">.+</Keywords>", "<Keywords name=""instre2"">$($strCmdLets)</Keywords>");
            
            ##### Verify Whether The New PowerShell Commands Line Is Different Than Before
            Write-Debug "Verify Whether The New PowerShell Commands Line Is Different Than Before";
            If ("$($arrLanges[$intLineIndex])" -ne "$($strNewLine)")
            {
                ##### Amend The Current PowerShell Commands Within Notepad++ Language File With The Enumerated Ones
                Write-Verbose "Amend The Current PowerShell Commands Within Notepad++ Language File With The Enumerated Ones";
                $arrLanges[$intLineIndex] = "$($strNewLine)";
                [System.Boolean]$bolExportNeeded = $true;
            }
        }
        
        #==================================================================================
        #endregion Enumerate And Update The PowerShell CmdLets
        #==================================================================================
        
        #==================================================================================
        #region Enumerate And Update The PowerShell Aliases
        #==================================================================================
        
        ##### Continue If PowerShell Language Section Begin Is Enumerated
        Write-Debug "Continue If PowerShell Language Section Begin Is Enumerated";
        [System.Int32]$intLineIndex = 0;
        If (($intPSIndex -gt 0) -and ($intPSIndex -lt $arrLanges.Count))
        {
            ##### Enumerate PowerShell Aliases Line
            Write-Debug "Enumerate PowerShell Aliases Line";
            For ([System.Int32]$intLineIndex = $intPSIndex; ($intLineIndex -lt $arrLanges.Count) -and ($intLineIndex -lt ($intPSIndex + 5)); $intLineIndex++)
            {
                ##### Verify Whether The Current Line Is PowerShell Aliases Line
                Write-Debug "Verify Whether The Current Line Is PowerShell Aliases Line";
                If ($arrLanges[$intLineIndex].Contains("<Keywords name=""type1"">")) { Break; }
            }
        }
        
        ##### Verify Whether PowerShell Aliases Line Is Enumerated
        Write-Debug "Verify Whether PowerShell Aliases Line Is Enumerated";
        If (($intLineIndex -le 0) -or ($intLineIndex -ge $arrLanges.Count) -or ($intLineIndex -le $intPSIndex) -or ($intLineIndex -ge ($intPSIndex + 5)))
        {
            Write-Error "Failed To Enumerate The PowerShell Aliases Section Within File: ""$($Path)""" -ErrorAction "Continue";
            Return;
        }
        
        ##### Continue If PowerShell Aliases Line Is Enumerated
        Write-Debug "Continue If PowerShell Aliases Line Is Enumerated";
        [System.String]$strAliases = "";
        If (($intLineIndex -gt 0) -and ($intLineIndex -lt $arrLanges.Count) -and ($intLineIndex -gt $intPSIndex) -and ($intLineIndex -lt ($intPSIndex + 5)))
        {
            ##### Verify Whether The Existing Aliases Have To Be Kept
            Write-Debug "Verify Whether The Existing Aliases Have To Be Kept";
            [System.String]$strTmp = "";
            If ($Keep.IsPresent)
            {
                ##### Enumerate The Existing Aliases Within Notepad++ Language File
                Write-Verbose "Enumerate The Existing Aliases Within Notepad++ Language File";
                [System.String]$strTmp = $arrLanges[$intLineIndex].Replace("<Keywords name=""type1"">", "").Replace("</Keywords>", "").Trim();
            }
            
            ##### Verify The Existing Aliases Enumeration
            ##### No Exit If No Any Aliases Either Notepad++ Might Come Without Aliases Or The User Might Want To Not Keep Them
            Write-Debug "Verify The Existing Aliases Enumeration";
            [System.String[]]$arrTmp = @();
            If (-not -not "$($strTmp)")
            {
                ##### Split The Notepad++ Aliases To Array
                Write-Debug "Split The Notepad++ Aliases To Array";
                [System.String[]]$arrTmp += $strTmp.Split(@(" "), [System.StringSplitOptions]::RemoveEmptyEntries);
            }
            
            ##### Filter The Enumerated PowerShell Commands Available On The Machine To Aliases
            Write-Verbose "Filter The Enumerated PowerShell Commands Available On The Machine To Aliases";
            [System.String[]]$arrTmp += $arrCommands | Where-Object {"$($_.CommandType)" -eq "Alias" } -Verbose:$false -Debug:$false | `
                Select-Object -Property @{ "n" = "Name"; "e" = { "$($_.Name)".ToLower() }; } -Verbose:$false -Debug:$false | `
                Select-Object -ExpandProperty "Name" -Verbose:$false -Debug:$false;
            #####
            
            ##### Filter Out The Repeatable Entries And Sort The Array
            Write-Debug "Filter Out The Repeatable Entries And Sort The Array";
            [System.String[]]$arrTmp = $arrTmp | Select-Object -Unique:$true -Verbose:$false -Debug:$false | Sort-Object -Verbose:$false -Debug:$false;
            
            ##### Verify Whether There Are Aliases For Including Into The Language File
            Write-Debug "Verify Whether There Are Aliases For Including Into The Language File";
            If ($arrTmp.Count -gt 0)
            {
                ##### Join The Aliases Array To A String Recognizable By Notepad++
                Write-Debug "Join The Aliases Array To A String Recognizable By Notepad++";
                [System.String]$strAliases = [System.String]::Join(" ", $arrTmp).ToLower();
            }
        }
        
        ##### Continue If PowerShell Aliases Line Is Enumerated
        Write-Debug "Continue If PowerShell Aliases Line Is Enumerated";
        If (($intLineIndex -gt 0) -and ($intLineIndex -lt $arrLanges.Count) -and ($intLineIndex -gt $intPSIndex) -and ($intLineIndex -lt ($intPSIndex + 5)))
        {
            ##### Initialize The New PowerShell Aliases Line
            Write-Debug "Initialize The New PowerShell Aliases Line";
            [System.String]$strNewLine = "";
            [System.String]$strNewLine = $arrLanges[$intLineIndex] -Replace ("<Keywords name=""type1"">.+</Keywords>", "<Keywords name=""type1"">$($strAliases)</Keywords>");
            
            ##### Verify Whether The New PowerShell Aliases Line Is Different Than Before
            Write-Debug "Verify Whether The New PowerShell Aliases Line Is Different Than Before";
            If ("$($arrLanges[$intLineIndex])" -ne "$($strNewLine)")
            {
                ##### Amend The Current PowerShell Aliases Within Notepad++ Language File With The Enumerated Ones
                Write-Verbose "Amend The Current PowerShell Aliases Within Notepad++ Language File With The Enumerated Ones";
                $arrLanges[$intLineIndex] = "$($strNewLine)";
                [System.Boolean]$bolExportNeeded = $true;
            }
        }
        
        #==================================================================================
        #endregion Enumerate And Update The PowerShell Aliases
        #==================================================================================
        
        #==================================================================================
        #region Export The Modified Notepad++ Languages File
        #==================================================================================
        
        ##### Continue If Export Is Needed
        Write-Debug "Continue If Export Is Needed";
        If ($bolExportNeeded)
        {
            ##### Rename The Original Language File
            [System.String]$strBackupFile = "";
            [System.String]$strBackupFile = "$([System.DateTime]::Now.ToString('yyyyMMddHHmmss'))_$([System.IO.Path]::GetFileName(""$($Path)""))_bkp";
            Write-Verbose ""; Write-Verbose "Rename The Original Notepad++ Language File To: ""$($strBackupFile)""";
            Rename-Item -Path "$($Path)" -NewName "$($strBackupFile)" -Force:$true -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false;
            
            ##### Export The New Notepad++ Language File
            Write-Verbose "Export The New Notepad++ Language File: ""$($Path)""";
            [System.String[]]$arrExport = @();
            [System.String[]]$arrExport = $arrLanges | Set-Content -Path "$($Path)" -Encoding "Ascii" -Force:$true -PassThru:$true -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false;
            
            ##### Compare The Value Which Has To Be Exported With The Actually Exported Value
            Write-Debug "Compare The Value Which Has To Be Exported With The Actually Exported Value";
            [System.Object[]]$arrCompare = @();
            [System.Object[]]$arrCompare = Compare-Object -ReferenceObject $arrLanges -DifferenceObject $arrExport -CaseSensitive:$true -Verbose:$false -Debug:$false;
            
            ##### Verify Whether The Discrepancies Between The Value Which Has To Be Exported And The Actually Exported One
            Write-Debug "Verify Whether The Discrepancies Between The Value Which Has To Be Exported And The Actually Exported One";
            If ($arrCompare.Count -gt 0)
            {
                Write-Error "Export Of Notepad++ Language File ""$($Path)"" Failed";
            } ElseIf ((@(Get-Process -Name "notepad++" -IncludeUserName -ErrorAction "SilentlyContinue" | Where-Object {"$($_.UserName)" -eq "$([System.Environment]::UserDomainName)\$([System.Environment]::UserName)"})).Count -gt 0)
            {
                Write-Warning "Please Restart Notepad++ In Order The Changes To Take Effect" -WarningAction "Continue";
            }
        } Else { Write-Verbose ""; Write-Verbose "Notepad++ Language File: ""$($Path)"" - No Changes Needed"; }
        
        #==================================================================================
        #endregion Export The Modified Notepad++ Languages File
        #==================================================================================
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrCompare";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrExport";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strBackupFile";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strAliases";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolExportNeeded";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strNewLine";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strCmdLets";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intLineIndex";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intI";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrTmp";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "strTmp";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrCommands";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "intPSIndex";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrLanges";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "arrExcludeFunctions";
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "bolVerbose";
        
        $PSCmdlet.MyInvocation.BoundParameters | Select-Object -ExpandProperty "Keys" | ForEach-Object { Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "$($_)"; }
    }
    
    ##### Override The End Method
    End
    {
        ##### Stop The StopWatch
        Write-Debug "Stop The StopWatch";
        $objCmdStopWatch.Stop();
        Write-Verbose ""; Write-Verbose "Command Elapsed: ""$($objCmdStopWatch.ElapsedMilliseconds)"" Milliseconds." ;
        
        ##### Reset The Variables
        Remove-Variable -Force:$true -WhatIf:$false -Confirm:$false -ErrorAction "SilentlyContinue" -Name "objCmdStopWatch";
        [System.GC]::Collect(); Write-Verbose "End!"; Write-Verbose "";
    }
}

#==================================================================================
#endregion Commands
#==================================================================================