SfBAutomatedLabTopology.psm1

Add-Type -TypeDefinition '
    using System;
 
    namespace SfBAutomatedLab
    {
        [Flags]
        public enum SfBServerRole
        {
            None = 0,
            FrontEnd = 1,
            Edge = 2,
            Mediation = 4,
            SqlServer = 8,
            WacService = 16,
            File = 32
        }
    }
'


function Import-SfBTopology
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )
    
    $script:tpContent = Get-Content -Path $Path
    $script:tp = [xml]$script:tpContent
    $script:tpFileName = $Path
    
    $script:ns = @{ 
        tp = 'urn:schema:Microsoft.Rtc.Management.Deploy.TopologyBuilder.2008'
        wt = 'urn:schema:Microsoft.Rtc.Management.Deploy.WritableTopology.2008'
        t = 'urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008'
    }
}

function Get-SfBTopology
{
    if (-not $script:tpFileName)
    {
        Write-Error "No SfB topology imported, use 'Import-SfBTopology' first."
    }
     
    New-Object psobject -Property ([ordered]@{
        Path = $script:tpFileName
        Content = $Script:tpContent
    })
}

function Get-SfBTopologySipDomain
{
    $script:tp | Select-Xml -XPath '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/t:InternalDomains/t:InternalDomain' -Namespace $script:ns | Select-Object -ExpandProperty Node
}

function Get-SfBTopologyActiveDirectoryDomains
{
    $fqdns = Get-SfBTopologyCluster | Get-SfBTopologyMachine | Select-Object -ExpandProperty Fqdn
    $fqdns | ForEach-Object { $_.Substring($_.IndexOf('.') + 1) } | Select-Object -Unique
}

function Get-SfBTopologyCentralSite
{
    param(
        [string[]]$SiteName
    )

    if ($SiteName)
    {
        foreach ($name in $SiteName)
        {
            $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite[t:Name = "{0}"]' -f $name
            $script:tp | Select-Xml -XPath $xPath -Namespace $script:ns |
            Select-Object -ExpandProperty Node |
            ForEach-Object {
                $_ | Add-Member -MemberType NoteProperty -Name CentralSiteName -Value $_.Name.'#text' -PassThru
            }
        }
    }
    else
    {
        $script:tp | Select-Xml -XPath '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite' -Namespace $script:ns |
        Select-Object -ExpandProperty Node |
        ForEach-Object {
            $_ | Add-Member -MemberType NoteProperty -Name CentralSiteName -Value $_.Name.'#text' -PassThru
        }
    }
}

function Get-SfBTopologyCluster
{
    [cmdletBinding(DefaultParameterSetName = 'ByXmlElement')]
    param(
        [Parameter(ValueFromPipeline, ParameterSetName = 'ByXmlElement')]
        [System.Xml.XmlElement[]]$CentralSite,

        [Parameter(ParameterSetName = 'ByName')]
        [string[]]$SiteName,

        [Parameter(ParameterSetName = 'ByFqdn')]
        [string[]]$Fqdn,

        [Parameter(ParameterSetName = 'ById')]
        [string[]]$Id
    )

    process
    {
        if ($SiteName)
        {
            $CentralSite = Get-SfBTopologyCentralSite -SiteName $SiteName
            if (-not $CentralSite)
            {
                Write-Error "The site '$SiteName' could not be found"
                return
            }
        }

        if (-not $CentralSite)
        {
            $CentralSite = Get-SfBTopologyCentralSite
        }

        if ($PSCmdlet.ParameterSetName -eq 'ByFqdn')
        {
            foreach ($item in $Fqdn)
            {
                $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite/wt:Clusters/wt:Cluster[translate(@Fqdn, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = "{0}"]' -f $item.ToLower()
            
                $script:tp | Select-Xml -XPath $xPath -Namespace $script:ns | 
                Select-Object -ExpandProperty Node       
            }     
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'ById')
        {
            foreach ($item in $Id)
            {
                $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite/wt:Clusters/wt:Cluster[translate(@UniqueId, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = "{0}"]' -f $item.ToString().ToLower()
            
                $script:tp | Select-Xml -XPath $xPath -Namespace $script:ns | 
                Select-Object -ExpandProperty Node
            }
        }
        else
        {
            foreach ($site in $CentralSite)
            {
                $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite[@SiteId = {0}]/wt:Clusters/wt:Cluster' -f $site.SiteId
            
                $script:tp | Select-Xml -XPath $xPath -Namespace $script:ns | 
                Select-Object -ExpandProperty Node
            }
        }
    }
}

function Get-SfBTopologyMachine
{
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Xml.XmlElement[]]$Cluster
    )

    process
    {
        if (-not $Cluster)
        {
            $CentralSite = Get-SfBTopologyCluster
        }

        foreach ($c in $Cluster)
        {
            $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite/wt:Clusters/wt:Cluster[@UniqueId = "{0}"]/wt:Machines/wt:Machine' -f $c.UniqueId
            
            $nodes = $script:tp | Select-Xml -XPath $xPath -Namespace $script:ns |
            Select-Object -ExpandProperty Node

            foreach ($node in $nodes)
            {
                $node | Add-Member -MemberType NoteProperty -Name ClusterFqdn -Value $c.Fqdn
                $node | Add-Member -MemberType NoteProperty -Name ClusterUniqueId -Value $c.UniqueId

                if (-not $node.Fqdn)
                {
                    $node | Add-Member -MemberType NoteProperty -Name Fqdn -Value $c.Fqdn
                }

                $node | Add-Member -MemberType NoteProperty -Name CentralSiteId -Value $node.ParentNode.ParentNode.SiteId
                $node | Add-Member -MemberType NoteProperty -Name CentralSiteName -Value $node.ParentNode.ParentNode.Name
                
                $role = [SfBAutomatedLab.SfBServerRole]::None

                if ($node.ParentNode.ParentNode.SqlInstances) { 
                    $role = $role -bor [SfBAutomatedLab.SfBServerRole]::SqlServer

                    # Add a second node to the output if we find an AlwaysOn configuration
                    if($node.ParentNode.ParentNode.SqlInstances.SqlInstance.HasAttribute('AlwaysOnPrimaryNodeFqdn'))
                    {
                        $HaPartnerFqdn =  $node.ParentNode.ParentNode.SqlInstances.SqlInstance.AlwaysOnPrimaryNodeFqdn                        
                        $HaNode = $node.PSObject.Copy()

                        $node | Add-Member -MemberType NoteProperty -Name AlwaysOnPartner -Value $HaPartnerFqdn

                        $HaNode.ClusterFqdn = $HaPartnerFqdn
                        $HaNode.Fqdn = $HaPartnerFqdn
                        $HaNode.FaultDomain = $HaPartnerFqdn
                        $HaNode.UpgradeDomain = $HaPartnerFqdn
                        $HaNode | Add-Member -Name Roles -MemberType NoteProperty -Value $role
                        $HaNode
                    }
                }
                if ($node.Fqdn -in (Get-SfBTopologyFileStore).InstalledOnMachines) { $role = $role -bor [SfBAutomatedLab.SfBServerRole]::File }
                if (Get-SfBTopologyCluster -Id $node.ClusterUniqueId | Get-SfBTopologyClusterService | Where-Object RoleName -eq UserServices) { $role = $role -bor [SfBAutomatedLab.SfBServerRole]::FrontEnd }
                if (Get-SfBTopologyCluster -Id $node.ClusterUniqueId | Get-SfBTopologyClusterService | Where-Object RoleName -eq EdgeServer) { $role = $role -bor [SfBAutomatedLab.SfBServerRole]::Edge }
                if (Get-SfBTopologyCluster -Id $node.ClusterUniqueId | Get-SfBTopologyClusterService | Where-Object RoleName -eq WacService) { $role = $role -bor [SfBAutomatedLab.SfBServerRole]::WacService }
                
                $node | Add-Member -Name Roles -MemberType NoteProperty -Value $role
                
                $node
            }
        }
    }
}

function Get-SfBTopologyFileStore
{
    $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:Services/wt:Service[@RoleName = "FileStore"]'
        
    $fileStores = $tp | Select-Xml -XPath $xPath -Namespace $ns | Select-Object -ExpandProperty Node
        
    foreach ($fileStore in $fileStores)
    {
        $fileStore | Add-Member -Name ShareName -MemberType NoteProperty -Value $fileStore.FileStoreService.ShareName
        $fileStore | Add-Member -Name InstalledOnCluster -MemberType NoteProperty -Value (Get-SfBTopologyCluster -Id $fileStore.InstalledOn)

        $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:CentralSites/wt:CentralSite/wt:Clusters/wt:Cluster[@UniqueId = "{0}"]/wt:Machines/wt:Machine' -f $fileStore.InstalledOn
            
        $nodes = $script:tp | Select-Xml -XPath $xPath -Namespace $script:ns |
        Select-Object -ExpandProperty Node

        $installedOnMachines = foreach ($node in $nodes)
        {
            if (-not $node.Fqdn)
            {
                $node.ParentNode.ParentNode.Fqdn
            }
            else
            {
                $node.Fqdn
            }
        }
        $fileStore | Add-Member -Name InstalledOnMachines -MemberType NoteProperty -Value $installedOnMachines

        $fileStore
    }
}

function Get-SfBMachineRoleString
{
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [PSObject]$Machine
    )

    $roleString = @()

    if (($Machine.Roles -band [SfBAutomatedLab.SfBServerRole]::File) -eq [SfBAutomatedLab.SfBServerRole]::File)
    {
        $roleString += 'FileServer'
    }

    if (($Machine.Roles -band [SfBAutomatedLab.SfBServerRole]::SqlServer) -eq [SfBAutomatedLab.SfBServerRole]::SqlServer)
    {
        $roleString += 'SqlServer2014'
    }
    
    if ($Machine.DomainRole -eq 'RootDC')
    {
        $roleString += 'RootDC, CaRoot'
    }
    elseif ($Machine.DomainRole -eq 'DC')
    {
        $roleString += 'DC'
    }
    elseif ($Machine.DomainRole -eq 'Office2016')
    {
        $roleString += 'Office2016'
    }
    elseif ($Machine.DomainRole -eq 'Exchange2016')
    {
        $roleString += 'Exchange2016'
    }

    if ($roleString)
    {
        ' -Roles ' + ($roleString -join ', ')
    }
}

function Get-SfBTopologyClusterService
{
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [PSObject]$Cluster
    )
    
    process
    {
        $xPath = '//tp:TopologyBuilder/tp:NewTopology/wt:PartialTopology/wt:Services/wt:Service[@InstalledOn = "{0}"]' -f $Cluster.UniqueId
        
        $tp | Select-Xml -XPath $xPath -Namespace $ns | Select-Object -ExpandProperty Node
    }
}