SkypeForBusinessHybridHealth.psm1

function Get-SkypeForBusinessHybridHealth{
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true,HelpMessage="Please enter your Office 365 domain:",Position=1,ParameterSetName="OnPrem")]$TenantDomain,
        [Parameter(Mandatory=$false,ParameterSetName="OnPrem")][PSCredential]$SkypeOnlineCredentials,
        [Parameter(Mandatory=$false,ParameterSetName="OnPrem")][switch]$OverrideAdminDomain,
        [Parameter(Mandatory=$false,ParameterSetName="Edge")][switch]$TestEdgeServer
    )

    begin{
        #import resources file
        try{
            Import-LocalizedData -BindingVariable Resources -FileName "SkypeForBusinessHybridHealthResources.psd1" -ErrorAction SilentlyContinue -ErrorVariable errorMessage
        }catch{
            throw;
        }

        if (!$TestEdgeServer){
            #validate required modules
            [array]$requiredModules = ($resources.RequiredModules).split(",")
            $requiredModules | ModuleValidation

            #get objects to perform tests
            $authDC = GetAuthDc
            $tenantInfo = GetTenantInfo -TenantDomain $TenantDomain
        }

        #start SFBO connection
        InvokeSkypeOnlineConnection
    }
    process{
        #tests
        if (!$TestEdgeServer){
            GetDomainControllerData
            GetForestData
            GetCmsReplicationStatus
            GetAccessEdgeConfiguration
            GetHostingProviderConfiguration
            GetTenantFederationConfiguration
            CompareFederationBetweenOnlineOnPrem
            TestFrontEndServers
            TestServerPatchVersion
        }

    }
    end{
        Get-PSSession | Remove-PSSession
    }
}


function ModuleValidation{
    [cmdletbinding()]
    Param
    (
        [Parameter(Mandatory=$false,ValueFromPipeline=$true)][array]$ModuleName
    )

    begin {}
    process {
        $foundModule = $true
        if (!(Get-Module $ModuleName)){
            #module is not loaded
            Write-Verbose -Message $($resources.SearchingModuleMessage + $ModuleName)
            $foundModule = Get-Module -ListAvailable | Where-Object Name -eq $ModuleName
            if ($foundModule){
                #module was found on the system
                Write-Verbose -Message $resources.ImportModuleMessage
                try{
                    Import-Module $ModuleName -ErrorAction SilentlyContinue -ErrorVariable moduleError
                }catch{
                    #import-module did not execute
                    throw $($ModuleName + ": " + ($resources.ModuleNoExecuteErrorMessage) + " " + $resources.$($ModuleName))
                }
                if ($moduleError){
                    #import-module executed but returned an error
                    throw $($ModuleName + ": " + ($resources.ModuleLoadErrorMessage) + " " + $resources.$($ModuleName))
                }
            }else{
                #module was not found and is not loaded
                 throw $($ModuleName + ": " + ($resources.ModuleNotFoundMessage) + " " +  $resources.$($ModuleName))
            }
        }
    }
    end {}
}

function GetAuthDc{
    [cmdletbinding()]
    Param()

    begin {}
    process {
        if (!$resources.DomainControllerComputerName){
            #parse domain controller from environment variables since no DC was given
            $authDC = ($env:LOGONSERVER).Replace("\\","") + "." + $env:USERDNSDOMAIN
            Write-Verbose -Message "$($resources.DomainControllerAuthMessage) $($authDC)"
        }else{
            #the user specified a Domain Controller in the resources file to use
            $authDC = $resources.DomainController
        }
    }
    end {
        return $authDC
    }
}

function GetDomainControllerData{
    [cmdletbinding()]
    Param()

    begin {}
    process {
        #perform PowerShell remoting to get DC version data.
        $commandToExecute = [scriptblock]::Create($resources.DomainControllerCheckCmd)
        try{
            $domainControllerData = Invoke-Command -ComputerName $authDC -ScriptBlock $commandToExecute -ErrorAction SilentlyContinue  -ErrorVariable dcErr
        }catch{
            #process this exception as the result
            $authDCResult = ProcessResult -testId $resources.DomainControllerTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $authDC -testErrorMessage $resources.DomainControllerErrorMessage -testExpectedValue $resources.DomainControllerMinimumVersion -testValue $_
            return
        }finally{
            if (!$dcErr){
                #didn't throw an exception and there was no error
                $dcVer = ($domainControllerData.OperatingSystemVersion).split(" ")[0] #parse version
                Write-Verbose -Message $($resources.DomainControllerMinimumVersionMessage + $dcVer)
    
                #compare expected version with discovered version
                $authDCResult = ProcessResult -testId $resources.DomainControllerTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $authDC -testErrorMessage $resources.DomainControllerErrorMessage -testSuccessMessage $resources.DomainControllerSuccessMessage -testExpectedValue $resources.DomainControllerMinimumVersion -testValue $dcVer
            }else{
                #there was an error, write the result
                $authDCResult = ProcessResult -testId $resources.DomainControllerTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $authDC -testErrorMessage $resources.DomainControllerErrorMessage -testExpectedValue $resources.DomainControllerMinimumVersion -testValue $dcErr.ErrorDetails.Message
            }
        }
    }
    end {
        return $authDCResult
    }
}

function GetForestData{
    [cmdletbinding()]
    Param()
    begin {

    }
    process {
        #perform PowerShell remoting to get Forest data.
        $commandToExecute = [scriptblock]::Create($resources.ForestModeCheckCmd)
        try{
            $forestData = Invoke-Command -ComputerName $authDC -ScriptBlock $commandToExecute -ErrorAction SilentlyContinue -ErrorVariable forestErr
        }catch{
            #process this exception as the test result
            $forestModeResult = ProcessResult -testId $resources.ForestModeTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $authDC -testErrorMessage $resources.ForestModeErrorMessage -testExpectedValue $resources.ForestModeExpectedVersion -testValue $_
            return
        }

        if (!$forestErr){
            #didn't throw an exception and there was no error
            Write-Verbose -Message $($resources.ForestModeMessage + $forestData.ForestMode)

            #compare expected version with discovered version
            $forestModeResult = ProcessResult -testId $resources.ForestModeTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $authDC -testErrorMessage $resources.ForestModeErrorMessage -testSuccessMessage $resources.ForestModeSuccessMessage -testExpectedValue $resources.ForestModeExpectedVersion -testValue $forestData.ForestMode
        }else{
            #there was an error, write the result
            $forestModeResult = ProcessResult -testId $resources.ForestModeTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $authDC -testErrorMessage $resources.ForestModeErrorMessage -testExpectedValue $resources.ForestModeExpectedVersion -testValue $forestErr.ErrorDetails.Message
        }


    }
    end {
        return $forestModeResult
    }
}

function GetCmsReplicationStatus{
    [cmdletbinding()]
    Param
    (
    [Parameter(Mandatory=$false)][string]$foo
    )

    begin {}
    process {
        try{
            $cmsReplicationResult = Get-CsManagementStoreReplicationStatus | Where-Object UpToDate -ne $true
        }catch{
            throw;
        }
        #walk through collection to build array of failed replication partners
        if ($cmsReplicationResult) {
            #we have failed replication servers
            $cmsTested = $cmsReplicationResult.ReplicaFqdn
        }else{
            $cmsTested = $resources.CMSReplicationExpectedResult
        }

        $cmsResult = ProcessResult -testId $resources.CMSReplicationTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.CMSReplicationErrorMessage -testSuccessMessage $resources.CMSReplicationSuccessMessage -testExpectedValue $resources.CMSReplicationExpectedResult -testValue $cmsTested
    }
    end {
        return $cmsResult
    }
}

function GetAccessEdgeConfiguration{
    [cmdletbinding()]
    Param()
    begin {}
    process {
        #### get Access Edge Configuration ###
        $accessEdgeConfig = Get-CsAccessEdgeConfiguration

        #check AllowOutsideUsers
        ProcessResult -testId $resources.AccessEdgeOutsideUsersTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.AccessEdgeOutsideUsersErrorMessage -testSuccessMessage $resources.AccessEdgeOutsideUsersSuccessMessage -testExpectedValue $resources.AccessEdgeOutsideUsers -testValue $accessEdgeConfig.AllowOutsideUsers
        #check AllowFederatedUsers
        ProcessResult -testId $resources.AccessEdgeFederatedUsersTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.AccessEdgeFederatedUsersErrorMessage -testSuccessMessage $resources.AccessEdgeFederatedUsersSuccessMessage -testExpectedValue $resources.AccessEdgeFederatedUsers -testValue $accessEdgeConfig.AllowFederatedUsers
        #check EnableParnterDiscovery
        ProcessResult -testId $resources.AccessEdgePartnerDiscoveryTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.AccessEdgePartnerDiscoveryErrorMessage -testSuccessMessage $resources.AccessEdgePartnerDiscoverySuccessMessage -testExpectedValue $resources.AccessEdgePartnerDiscovery -testValue $accessEdgeConfig.EnablePartnerDiscovery
        #checkUseDnsSrvRouting
        ProcessResult -testId $resources.AccessEdgeDnsSrvRoutingTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.AccessEdgeDnsSrvRoutingErrorMessage -testSuccessMessage $resources.AccessEdgeDnsSrvRoutingSuccessMessage -testExpectedValue $resources.AccessEdgeDnsSrvRouting -testValue $accessEdgeConfig.RoutingMethod        
    }
    end {}
}

function GetHostingProviderConfiguration{
    [cmdletbinding()]
    Param()
    
    begin {}
    process {
        #### get Hosting Provider Configuration ###
        $hostingProviderConfig = Get-CsHostingProvider | Where-Object ProxyFqdn -eq $resources.HostingProviderProxyFqdn

        #since we can get back multiple objects from Get-CsHostingProvider we perform the filter above. Since the 'Identity' and 'Name' values for this object are subject to change, we just need to verify the ProxyFqdn is set correctly on one of the objects returned.
        if ($hostingProviderConfig){
            #check Proxy FQDN
            ProcessResult -testId $resources.HostingProviderProxyFqdnTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderProxyFqdnErrorMessage -testSuccessMessage $resources.HostingProviderProxyFqdnSuccessMessage -testExpectedValue $resources.HostingProviderProxyFqdn -testValue $hostingProviderConfig.ProxyFqdn
            #check Enablement
            ProcessResult -testId $resources.HostingProviderEnabledTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderEnabledErrorMessage -testSuccessMessage $resources.HostingProviderEnabledSuccessMessage -testExpectedValue $resources.HostingProviderEnabled -testValue $hostingProviderConfig.Enabled
            #check Shared Address Space
            ProcessResult -testId $resources.HostingProviderSharedAddressSpaceTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderSharedAddressSpaceErrorMessage -testSuccessMessage $resources.HostingProviderSharedAddressSpaceSuccessMessage -testExpectedValue $resources.HostingProviderSharedAddressSpace -testValue $hostingProviderConfig.EnabledSharedAddressSpace
            #check Hosts OCS Users
            ProcessResult -testId $resources.HostingProviderHostOCSUsersTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderHostOCSUsersErrorMessage -testSuccessMessage $resources.HostingProviderHostOCSUsersSuccessMessage -testExpectedValue $resources.HostingProviderHostOCSUsers -testValue $hostingProviderConfig.HostsOCSUsers
            #check Verification level
            ProcessResult -testId $resources.HostingProviderVerificationLevelTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderVerificationLevelErrorMessage -testSuccessMessage $resources.HostingProviderVerificationLevelSuccessMessage -testExpectedValue $resources.HostingProviderVerificationLevel -testValue $hostingProviderConfig.VerificationLevel
            #check IsLocal
            ProcessResult -testId $resources.HostingProviderIsLocalTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderIsLocalErrorMessage -testSuccessMessage $resources.HostingProviderIsLocalSuccessMessage -testExpectedValue $resources.HostingProviderIsLocal -testValue $hostingProviderConfig.IsLocal
            
            ### NOTE:check AutoDiscoverUrl obtained from GetTenantInfo function
            ProcessResult -testId $resources.HostingProviderUrlTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderUrlErrorMessage -testSuccessMessage $resources.HostingProviderUrlSuccessMessage -testExpectedValue $tenantInfo -testValue $hostingProviderConfig.AutoDiscoverUrl
        }else{
            #we didn't find a match for the Hosting Provider
            ProcessResult -testId $resources.HostingProviderTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.HostingProviderErrorMesssage
        }

    }
    end {}
}

function GetTenantFederationConfiguration{
    [cmdletbinding()]
    Param()

    begin {}
    process {
        $tenantFedConfig = Get-SfboCsTenantFederationConfiguration
        ProcessResult -testId $resources.TenantSharedSipTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.TenantSharedSipErrorMessage -testSuccessMessage $resources.TenantSharedSipSuccessMessage -testExpectedValue $resources.TenantSharedSip -testValue $tenantFedConfig.SharedSipAddressSpace
    }
    end {}
}

function InvokeSkypeOnlineConnection{
    [cmdletbinding()]
    Param()
    begin{}
    process{
        #determine if Skype for Business PsSession is loaded in memory
        $sessionInfo = Get-PsSession

        #remove any PSSession previously established
        foreach ($sessionItem in $sessionInfo){
            if ($sessionItem.ComputerName.Contains(".online.lync.com")){
                Write-Verbose -Message $resources.PSSessionRemovalMessage
                $sessionItem | Remove-PSSession
            }
        }

        Write-Verbose -Message $resources.NewSkypeOnlineSessionMessage

        try{
            if (!$SkypeOnlineCredentials){
                Write-Output $resources.SFBONoCredsMessage
                if (!$OverrideAdminDomain){
                    $lyncsession = New-CsOnlineSession -ErrorAction SilentlyContinue -ErrorVariable $newOnlineSessionError
                }else{
                    $lyncsession = New-CsOnlineSession -ErrorAction SilentlyContinue -OverrideAdminDomain $TenantDomain -ErrorVariable $newOnlineSessionError
                }
            }else{
                if (!$OverrideAdminDomain){
                    $lyncsession = New-CsOnlineSession -Credential $SkypeOnlineCredentials -ErrorAction SilentlyContinue -ErrorVariable $newOnlineSessionError
                }else{
                    $lyncsession = New-CsOnlineSession -Credential $SkypeOnlineCredentials -OverrideAdminDomain $TenantDomain -ErrorAction SilentlyContinue -ErrorVariable $newOnlineSessionError
                }
            }
        }catch{
            throw $_
        }finally{
            if ($newOnlineSessionError){
                throw $newOnlineSessionError
            }    
        }

        Write-Verbose -Message $resources.ImportingPSSessionMessage
        try{
            Import-PSSession $lyncsession -Prefix Sfbo -ErrorAction SilentlyContinue -ErrorVariable $psSessionError | Out-Null
        }
        catch{
            throw
        }
        
    }
    end{}
}

function ProcessResult{
    [cmdletbinding()]
    Param
    (
        [Parameter(Mandatory=$true)][string]$testId,
        [Parameter(Mandatory=$true)][string]$testName,
        [Parameter(Mandatory=$false)]$sourceComputerName,
        [Parameter(Mandatory=$false)]$destinationComputerName,
        [Parameter(Mandatory=$false)][string]$testErrorMessage,
        [Parameter(Mandatory=$false)][string]$testSuccessMessage,
        [Parameter(Mandatory=$true)]$testExpectedValue,    
        [Parameter(Mandatory=$true)]$testValue
    )

    begin {}
    process {
        [array]$outputResult = [PSCustomObject][ordered]@{
            TestId = $testId
            TestName = $testName
            SourceComputerName = $sourceComputerName
            DestinationComputerName = $destinationComputerName
            ErrorMessage = $(if ($testExpectedValue -ne $testValue){$testErrorMessage}else{$testErrorMessage = $null})
            SuccessMessage = $(if ($testExpectedValue -eq $testValue){$testSuccessMessage}else{$testSuccessMessage = $null})
            ExpectedValue = $testExpectedValue
            TestedValue = $testValue
            TestedResult = $(if ($testExpectedValue -ne $testValue){"FAIL"}else{"PASS"})
            TestDate = $(Get-Date)
        }
    }
    end {
        return $outputResult
    }
}

function TestFrontEndServers{
    [cmdletbinding()]
    Param
    ()

    begin {
        $testId = $resources.FeServerPortTestId
    }
    process {
        #find all Edge servers to test FE to EDGE association
        [array]$edgeServers = ((Get-CsService -EdgeServer).Identity).Replace("EdgeServer:","")

        #note: we don't do a Get-CsService -Registrar here because we want the associated FE's for all Edge servers. Some FE's might not have an Edge association defined.
        [array]$poolServers = (((Get-CsService -EdgeServer).DependentServiceList) | Where-Object {$_ -like "Registrar:*"}).Replace("Registrar:","")
        $registrarServers = ($poolServers | ForEach-Object {Get-CsPool -Identity $_}).Computers
        [array]$frontEndToEdgePorts = ($resources.FeServerPortTestList).split(",")
        $edgeServerTestResults = TestTcpPortConnection -Ports $frontEndToEdgePorts -Source $registrarServers -Destination $edgeServers
        $edgeServerTestResults | ForEach-Object {
            ProcessResult -testId $resources.FeServerPortTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $_.PSComputerName -destinationComputerName $_.Destination -testErrorMessage $($resources.FeServerPortTestErrorMessage + $_.Port) -testSuccessMessage $($resources.FeServerPortTestSuccessMessage + $_.Port) -testExpectedValue $resources.PortTestExpected -testValue $_.TestResult
        }
    }
    end {}
}

function CompareFederationBetweenOnlineOnPrem {
    [cmdletbinding()]
    param(

    )
    begin{
        #make sure SFBO connection is established
        $session = Get-PSSession | Where-Object {$_.ComputerName -like "*online.lync.com" -and $_.State -eq "Opened" -and $_.Availability -eq "Available"} -ErrorAction SilentlyContinue
        if (!$session){
            return ProcessResult -testId $resources.CompareFederationSettingsTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.NoSfboConnection
        }
    }
    process{
        if (!($fedResult = Get-SfboCsTenantFederationConfiguration).AllowFederatedUsers){
            #federation is turned off in the tenant
            return ProcessResult -testId $resources.CompareFederationSettingsTestId -testName $PSCmdlet.CommandRuntime -testErrorMessage $resources.CompareFederationSettingsOnlineFedError -testExpectedValue $resources.CompareFederationSettingsAllowFed -testValue $fedResult.AllowFederatedUsers
        }else{
            ProcessResult -testId $resources.CompareFederationSettingsTestId -testName $PSCmdlet.CommandRuntime -testSuccessMessage $resources.CompareFederationSettingsSuccessMessage -testExpectedValue $resources.CompareFederationSettingsAllowFed -testValue $fedResult.AllowFederatedUsers
        }

    }
    end{}

}

function TestTcpPortConnection{
    [cmdletbinding()]
    param(
        [Parameter(mandatory=$true)][array]$Source,
        [Parameter(mandatory=$true)][array]$Destination,
        [Parameter(mandatory=$true)][array]$Ports,
        [Parameter(Mandatory=$false)][int32]$TimeoutInMs = 1000
    )
    begin{}
    process{
        ForEach ($d in $Destination){
            #we should remove the source server from the array just in case since we don't want to test from/to the same server
            If ($Source.Contains($d)){
                Write-Verbose -Message $resources.PortTestRemoveDestinationMessage
                [System.Collections.ArrayList]$NewSource = $Source #alternatively we could use $NewSource = $Source -ne $d
                $NewSource.Remove($d)
            }else{
                $NewSource = $Source
            }
            ForEach ($p in $Ports){
                try{
                    [array]$portTestResult += Invoke-Command -ScriptBlock {
                        $Socket = New-Object System.Net.Sockets.TCPClient;
                        $Connection = $Socket.BeginConnect($args[0],$args[1],$null,$null);
                        $Connection.AsyncWaitHandle.WaitOne($args[2],$false)  | Out-Null;
                        $bucket = [PSCustomObject]@{
                            Source = ($env:COMPUTERNAME).ToLower()
                            Destination = ($args[0]).ToLower()
                            Port = $args[1]
                            TestResult = $(if($Socket.Connected -eq $true){"Connected"}else{"Not Connected"})
                        }
                        $Socket.Close | Out-Null;
                        Return $bucket;
                        $bucket = $null;
                    } -ComputerName $NewSource -Args $d,$p,$TimeoutInMs -ErrorAction SilentlyContinue -ErrorVariable $testTcpError
                }catch{
                    ProcessResult -testId $testId -testName $PSCmdlet.CommandRuntime -sourceComputerName $_.PSComputerName -destinationComputerName $d -testErrorMessage $_ -testExpectedValue $resources.PortTestExpected -testValue "Exception"
                }finally{
                    if ($testTcpError){
                        ProcessResult -testId $testId -testName $PSCmdlet.CommandRuntime -sourceComputerName $_.PSComputerName -destinationComputerName $d -testErrorMessage $_ -testExpectedValue $resources.PortTestExpected -testValue "Error"
                    }
                }
            }

            
        }
    }
    end{
        return $portTestResult
    }
}

function TestServerPatchVersion{
    [cmdletbinding()]
    Param()
    begin {
        #get ServicesToPatch from resources file. to scan for more simply add them to the resources file then add to the switch block below for each service's text to display.
        [array]$servicesToPatch = $resources.ServicesToPatch.split(",")
    }
    process {
        foreach ($serviceItem in $servicesToPatch){
            switch ($serviceItem){
                Registrar {$serviceFriendlyName = $resources.FeServiceFriendlyName;$serviceName = $resources.FeServiceName;$patchTestId = $resources.FeServerPatchVersionTestId;$patchErrorMessage = $($resources.FeServerPatchErrorMessage + $resources.FeServiceFriendlyName);$patchSuccessMessage = $($resources.FeServerPatchSuccessMessage + $resources.FeServiceFriendlyName);$patchVersion = $resources.FeServerPatchVersion}
                MediationServer {$serviceFriendlyName = $resources.MedServiceFriendlyName;$serviceName = $resources.MedServiceName;$patchTestId = $resources.MedServerPatchVersionTestId;$patchErrorMessage = $($resources.MedServerPatchErrorMessage = $resources.MedServiceFriendlyName);$patchSuccessMessage = $($resources.MedServerPatchSuccessMessage + $resources.MedServiceFriendlyName);$patchVersion = $resources.MedServerPatchVersion}
            }
            
            $expr = "Get-CsService -$serviceItem"
            $servers = (Invoke-Expression $expr).Identity.Replace($serviceItem + ":","")
            try{
                #powershell remoting used to fan out for speed
                $patchResult = (Invoke-Command -ScriptBlock {Get-CsServerPatchVersion} -ComputerName $servers -ErrorAction SilentlyContinue -ErrorVariable patchError | Where-Object ComponentName -eq $serviceFriendlyName)
            }catch{
                ProcessResult -testId $patchTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $_.PSComputerName -testErrorMessage $_ -testExpectedValue $patchVersion -testValue "Exception"
            }finally{
                if ($patchError){
                    ProcessResult -testId $patchTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $_.PSComputerName -testErrorMessage $patchError -testExpectedValue $patchVersion -testValue "Error"
                }
            }
            
            
            #process results for this service item
            $patchResult | ForEach-Object {
                ProcessResult -testId $patchTestId -testName $PSCmdlet.CommandRuntime -sourceComputerName $_.PSComputerName -testErrorMessage $patchErrorMessage -testSuccessMessage $patchSuccessMessage -testExpectedValue $patchVersion -testValue $_.Version
            }
        }
    }
    end {}
}

function GetTenantInfo{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 1)][ValidateNotNull()][string] $TenantDomain,
        [Parameter(Mandatory = $false)][Switch] $Edog = $false,
        [Parameter(Mandatory = $false)][string] $ForestFQDN,
        [Parameter(Mandatory = $false)][string] $altForestFQDN = $null,
        [Parameter(Mandatory = $false)][string] $acsFQDN = $null
    )
    begin{
        $ErrorActionPreference = "Stop"
    }

    process{
        if ([System.String]::IsNullOrEmpty($ForestFQDN)){
            if ($Edog)
            {
                $ForestFQDN = "webdir.tip.lync.com"
            }
            else
            {
                $ForestFQDN = "webdir.online.lync.com"
            }
        }

        if ([System.String]::IsNullOrEmpty($acsFQDN)){
            if ($Edog)
            {
                $acsFQDN = "accounts.accesscontrol.windows-ppe.net"
            }
            else
            {
                $acsFQDN = "accounts.accesscontrol.windows.net"
            }
        }

        # First validate that domain is provisioned on O365 ACS and output Domain and tenant ID
        try{
            $req = [Net.WebRequest]::Create("https://$acsFQDN/metadata/json/1?realm=$TenantDomain")
            Write-Verbose ("Getting ACS json document from {0} ..." -f $req.RequestUri)
            $rsp = $req.GetResponse()
            $str = (new-object System.IO.StreamReader ($rsp.GetResponseStream())).ReadToEnd()
            $TenantID = ($str | ConvertFrom-Json).realm
        }catch [System.Net.WebException]{
            $webEx = ($Error[0].Exception.InnerException) -as [System.Net.WebException]
            if (($webEx -ne $null) -and ($webEx.Status -eq [System.Net.WebExceptionStatus]::ProtocolError)){
                throw "Domain $TenantDomain is not registired with ACS/O365"
            }
            throw
        }catch{
            throw
        }

        # Now get response from Lync SfB autodiscover service
        $req = [Net.WebRequest]::Create("https://$ForestFQDN/AutoDiscover/AutoDiscoverservice.svc/root?originalDomain=$TenantDomain")
        $rsp = $req.GetResponse()
        $str = (new-object System.IO.StreamReader ($rsp.GetResponseStream())).ReadToEnd()
        #Write-Verbose $str

        $json = ($str | ConvertFrom-Json)
        $self = ($json._links.self.href -as [System.URI]).Host
        if ([System.String]::IsNullOrEmpty($json._links.redirect.href)){
            # Since we were not redirected to a different forest, we need to make sure
            # that domain is actually in Lync/SfB online by asking some other forest
            if ([System.String]::IsNullOrEmpty($altForestFQDN)){
                switch ($self){
                    # Production directors
                    "webdir0a.online.lync.com" {$altForestFQDN = "webdir0b.online.lync.com"}
                    "webdir0b.online.lync.com" {$altForestFQDN = "webdir0e.online.lync.com"}
                    "webdir0e.online.lync.com" {$altForestFQDN = "webdir0f.online.lync.com"}
                    "webdir0f.online.lync.com" {$altForestFQDN = "webdir0m.online.lync.com"}
                    "webdir0m.online.lync.com" {$altForestFQDN = "webdir0a.online.lync.com"}
                    "webdir1a.online.lync.com" {$altForestFQDN = "webdir1b.online.lync.com"}
                    "webdir1b.online.lync.com" {$altForestFQDN = "webdir1e.online.lync.com"}
                    "webdir1e.online.lync.com" {$altForestFQDN = "webdir2a.online.lync.com"}
                    "webdir2a.online.lync.com" {$altForestFQDN = "webdir0a.online.lync.com"}
                    "webdirAU1.online.lync.com" {$altForestFQDN = "webdirIN1.online.lync.com"}
                    "webdirIN1.online.lync.com" {$altForestFQDN = "webdirJP1.online.lync.com"}
                    "webdirJP1.online.lync.com" {$altForestFQDN = "webdirAU1.online.lync.com"}

                    # EDOG directors
                    "webdir0d.tip.lync.com" {$altForestFQDN = "webdir1d.tip.lync.com"}
                    "webdir1d.tip.lync.com" {$altForestFQDN = "webdir0d.tip.lync.com"}

                    # Unkown servers
                    default{
                        if ($self.EndsWith("online.lync.com")){
                            $altForestFQDN = "webdir0m.online.lync.com"
                        }elseif ($self.EndsWith("tip.lync.com")){
                            $altForestFQDN = "webdir0d.tip.lync.com"
                        }else{
                            throw "Unknown forest FQDN: $self"
                        }
                    }
                }
                Write-Verbose "Selected forest $altForestFQDN for second check"
            }else{
                Write-Verbose "Using forest $altForestFQDN for second check"
            }

            $req = [Net.WebRequest]::Create("https://$altForestFQDN/AutoDiscover/AutoDiscoverservice.svc/root?originalDomain=$TenantDomain")
            $rsp = $req.GetResponse()
            $str = (new-object System.IO.StreamReader ($rsp.GetResponseStream())).ReadToEnd()
            #Write-Verbose $str

            $json = ($str | ConvertFrom-Json)
            $altSelf = ($json._links.self.href -as [System.URI]).Host
        }
        if ([System.String]::IsNullOrEmpty($json._links.redirect.href)){
            throw "Domain $TenantDomain is not in any known SfB/Lync online forest (reported by $self and $altSelf)"
        }

        $redirect = ($json._links.redirect.href -as [System.URI]).Host
        Write-Verbose "Domain $TenantDomain is in $redirect, reported by $self"
        $tenantForest = $redirect

        $req = [Net.WebRequest]::Create("https://$redirect/WebTicket/WebTicketService.svc/mex")
        $req.Headers.Add("X-User-Identity", (-join "user@",$TenantDomain))
        $rsp = $req.GetResponse()
        $str = (new-object System.IO.StreamReader ($rsp.GetResponseStream())).ReadToEnd()
        #Write-Verbose $str

        $namespace = @{
            wsdl="http://schemas.xmlsoap.org/wsdl/"; 
            wsx="http://schemas.xmlsoap.org/ws/2004/09/mex"; 
            wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
            wsa10="http://www.w3.org/2005/08/addressing";
            wsp="http://schemas.xmlsoap.org/ws/2004/09/policy";
            wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy";
            msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract";
            soap12="http://schemas.xmlsoap.org/wsdl/soap12/";
            wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"; 
            wsam="http://www.w3.org/2007/05/addressing/metadata"; 
            xsd="http://www.w3.org/2001/XMLSchema"; 
            tns="http://tempuri.org/"; 
            soap="http://schemas.xmlsoap.org/wsdl/soap/"; 
            wsaw="http://www.w3.org/2006/05/addressing/wsdl"; 
            soapenc="http://schemas.xmlsoap.org/soap/encoding/";
            af="urn:component:Microsoft.Rtc.WebAuthentication.2010"
        }

        $TenantOAuth = (Select-Xml -Namespace $namespace -Content $str -XPath "//af:OAuth/@af:authorizationUri").Node.Value

        #Write Output Oject
        $properties = @{
            'TenantDomain'=$TenantDomain;
            'TenantID'=$TenantID;
            'TenantForest'=$TenantForest;
            'TenantOAuth'=$TenantOAuth;
        }
        $object = New-Object -TypeName PSObject -Property $properties

        #prepare autodiscoverurl
        $autodiscoverUrl = "https://$($object.TenantForest)/Autodiscover/AutodiscoverService.svc/root"
    }
    end{
        return $autodiscoverUrl
    }
}

function Template{
    [cmdletbinding()]
    Param
    (
    [Parameter(Mandatory=$false)][string]$foo
    )

    begin {}
    process {

    }
    end {}
}