Create-BuildingMappingFromADSites.ps1

<#PSScriptInfo
 
.VERSION 0.9
 
.GUID 0cf0112f-96e2-4612-bea7-083ef943c249
 
.AUTHOR martrin@microsoft.com
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
#>


<#
.SYNOPSIS
Retrieves AD Site and Subnet information and creates a subnet mapping file for Call Quality Dashboard (CQD).
 
.DESCRIPTION
The Create-BuildingMappingFromADSites script iterates through all AD Sites and Subnets. All valid IPv4 addresses are exported into a CSV-style format, all other formats like IPv6 as skipped as CQD doesn't support them today.
You can use the optional parameters to specify additional information for the mapping file, including City, Country, Region and all other data fields that CQD supports.
 
The script requires the computer to be domain joined so that it can leverage an AD context to retrieve data. Computers joined to Azure Active Directory aren't supported.
 
.EXAMPLE
Create-BuildingMappingFromADSites.ps1
Read AD site and subnet information and create building mapping file.
 
.EXAMPLE
Create-BuildingMappingFromADSites.ps1 -Filename 'MyFile.csv' -BuildingOfficeType 'CompanyOwned'
Read AD site and subnet information, use filename MyFile.csv and assign BuildingOfficeType = CompanyOwned
 
.EXAMPLE
Create-BuildingMappingFromADSites.ps1 -ExpressRoute
Read AD site and subnet information, mark all sites to be connected via ExpressRoute
 
.Link
https://docs.microsoft.com/en-us/SkypeForBusiness/using-call-quality-in-your-organization/turning-on-and-using-call-quality-dashboard#upload-building-information
 
.NOTES
File Name: Create-BuildingMappingFromADSites.ps1
Author: Martin Rinas (martrin@microsoft.com)
 
You need to run this script from a Domain joined machine, AAD joined machines or machines in a workgroup aren't supported.
#>

[cmdletbinding()]            
param(
    [Parameter(Mandatory=$false)]
    [string]$FileName='.\BuildingFile.csv',
    [Parameter(Mandatory=$false)]
    [string]$NetworkName='',    
    [Parameter(Mandatory=$false)]
    [string]$OwnershipType='',
    [Parameter(Mandatory=$false)]
    [string]$BuildingType='',
    [Parameter(Mandatory=$false)]
    [string]$BuildingOfficeType='',
    [Parameter(Mandatory=$false)]
    [string]$City='',
    [Parameter(Mandatory=$false)]
    [string]$ZipCode='',
    [Parameter(Mandatory=$false)]
    [string]$Country='',
    [Parameter(Mandatory=$false)]
    [string]$State='',
    [Parameter(Mandatory=$false)]
    [string]$Region='',
    [Parameter(Mandatory=$false)]
    [Switch]$ExpressRoute
)            

#region define variables and prepare for execution

# RegEx to detect a single IPv4 Octet
New-Variable -Name Octet -Value  '(?:0?0?[0-9]|0?[1-9][0-9]|1[0-9]{2}|2[0-5][0-5]|2[0-4][0-9])' -Option ReadOnly
# and we need four of them for a valid IPv4 address, combine them into a RegEx
[regex] $IPv4Regex = "^(?:$Octet\.){3}$Octet$"

# Cannot process Switch parameter during export, have to convert it into bool.
if($ExpressRoute)
{
    [bool]$ExpressRoute = $true
}
else 
{
    [bool]$ExpressRoute = $false
        
}

# Test if output file already exist, let's not override any existing file
if(Test-Path($FileName))
{
    $ExistingFile = get-item $FileName
    Write-Host ('File {0} already exists, please remove existing file.' -f $ExistingFile.FullName)   -ForegroundColor Red
    break
}
#endregion

#region main script, process AD data and create mapping file
# get all Sites from currenct AD Forest
try {
    Write-Verbose 'connecting to current AD forest'
    $CurrentForestName = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Name
    $Sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites                   
}
catch {
    # This is a fatal error. Cannot continue without connectivity to AD.
    Write-Host 'Could not connect to Active Directory.' -ForegroundColor red
    Write-Host $_.Exception.Message -ForegroundColor Yellow
    Write-Verbose ('error: {0}' -f $_.Exception)
    break
}

Write-Host ('Processing {0} sites from {1}' -f $sites.count, $CurrentForestName) -ForegroundColor Yellow

# Create the empty array as a container for the mapping file
$SubnetMappingFile = @()

# Looping through all sites
foreach ($Site in $Sites) {            

    write-verbose ('processing site: {0}' -f $site.name)
    write-verbose ('found {0} subnets' -f $site.subnets.count)
    
    # Processing all subnets within the same site
    foreach ($subnet in $site.Subnets)
    {
        write-verbose ('processing subnet: {0}' -f $subnet.name)
        
        #split Subnet into Address and mask
        $SplitSubnet = $subnet.name.split("/")

        if ($SplitSubnet.count -ne 2)
        {
            # testing if split was successufl
            write-verbose ('Cannot split {0} into Subnet ID and mask, skipping..."' -f $Subnet.name)
            continue
        }
        
        # is this a valid IPv4 address? CQD doesn't support IPv6
        if(!($SplitSubnet[0] -match $IPv4Regex))
        {
            # not a IPv4 address, continue with next subnet for same site
            write-verbose ('skipping subnet, not a valid IPv4 address: {0}' -f $SplitSubnet)
            continue
        }
        
        # create and populate our object
        $Row = New-Object System.Object
        
        # replacing any comma character ',' in the sting with an underscore '_' so that the comma doesn't mess up with the CSV export
        # AD doesn't allow any special characters in the site name, no special handling required there.
        $Row | Add-Member -MemberType NoteProperty -Name Network -Value ([string]$SplitSubnet[0])
        $Row | Add-Member -MemberType NoteProperty -Name NetworkName -Value  $NetworkName.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name NetworkRange -Value ([int]$SplitSubnet[1])
        $Row | Add-Member -MemberType NoteProperty -Name BuildingName -Value ([string]$Subnet.Site)
        $Row | Add-Member -MemberType NoteProperty -Name OwnershipType -Value $OwnershipType.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name BuildingType -Value $BuildingType.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name BuildingOfficeType -Value $BuildingOfficeType.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name City -Value $City.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name ZipCode -Value $ZipCode.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name Country -Value $Country.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name State -Value $State.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name Region -Value $Region.replace(',','_')
        $Row | Add-Member -MemberType NoteProperty -Name InsideCorp -Value '1'
        $Row | Add-Member -MemberType NoteProperty -Name ExpressRoute -Value ([int]$ExpressRoute)

        # Add results to mapping file
        $SubnetMappingFile += $Row
    }
}

# Convert into CSV format
$SubnetMappingFile = $SubnetMappingFile | convertto-csv -NoTypeInformation -Delimiter ','

# CQD doesn'T expect quotes in the text, so we have to remove them
$SubnetMappingFile = $SubnetMappingFile.Replace('"','')

Write-Host ('Saving building mapping file: {0}' -f $FileName ) -ForegroundColor Green

# CDQ also doesn't expect column headings, so we remove them and save the remaining content to disk
$SubnetMappingFile[1..$SubnetMappingFile.Length] | Set-Content -Path $FileName -ErrorAction Stop

Write-Host 'Please validate the accuracy, adjust and complete as needed.' -ForegroundColor Green
Write-Host 'You may use the graphical interface of the Network Planner at https://aka.ms/MyAdvisor to manipulate the file as needed'
Write-Host 'Upload the file to CQD at https://cqd.lync.com to complete the process.'

#endregion