Public/IaaS/networking/New-CmAzIaasNetworking.ps1

function New-CmAzIaasNetworking {
    <#
        .Synopsis
        Creates networking solution
 
        .Description
        Completes following:
            * Creates vnets and subnets. Optionally attach nsg and route tables to subnet.
            * Creates route tables and routes.
            * Creates network security groups.
            * Configure resources in mulitple resource groups at once.
 
        .Parameter SettingsFile
        File path for the settings file to be converted into a settings object.
 
        .Parameter VnetCsvFile
        File path for the csv containing virtual network configurations.
        Required headers: resourceGroupName|Location|vnetName|addressSpace|subnetName|cidr|networkSecurityGroup|routeTable
 
        .Parameter RouteTableCsvFile
        File path for the csv containing route table configurations.
        Required headers: resourceGroupName|tableName|routeName|cidr|nextHopType|nextHopIpAddress|notes
 
        .Parameter NsgCsvFile
        File path for the csv containing virtual network security group configurations.
        Required headers: resourceGroupName|nsgName|ruleName|priority|direction|sourceIp|sourcePort|destinationIp|destinationPort|protocol|Access|Description
 
        .Component
        IaaS
 
        .Example
        New-CmAzIaasNetworking -settingsFile "networking.yml"
 
        .Example
        New-CmAzIaasNetworking -VnetCsvFile "vnet.csv" -RouteTableCsvFile "routeTable.csv" -NsgCsvFile "nsg.csv"
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param(
        [parameter(Mandatory = $true, ParameterSetName = "Settings Yml File")]
        [String]$SettingsFile,
        [parameter(Mandatory = $false, ParameterSetName = "Settings CSV File")]
        [String]$VnetCsvFile,
        [parameter(Mandatory = $false, ParameterSetName = "Settings CSV File")]
        [String]$RouteTableCsvFile,
        [parameter(Mandatory = $false, ParameterSetName = "Settings CSV File")]
        [String]$NsgCsvFile
    )

    $ErrorActionPreference = "Stop"

    try {

        if ($PSCmdlet.ShouldProcess((Get-CmAzSubscriptionName), "Create networking solution")) {

            if ($SettingsFile -and -not $SettingsObject -and -not $VnetCsvFile -and -not $RouteTableCsvFile -and -not $NsgCsvFile ) {
                Write-Verbose "Importing setting from Yml file"
                $SettingsObject = Get-CmAzSettingsFile -Path $SettingsFile
            }
            elseif (-not $SettingsFile -and -not $SettingsObject -and -not $VnetCsvFile -and -not $RouteTableCsvFile -and -not $NsgCsvFile ) {
                Write-Error "No valid input settings." -Category InvalidArgument -CategoryTargetName "SettingsObject"
            }

            # Code to Create Object from CSV

            if ($VnetCsvFile -or $RouteTableCsvFile -or $NsgCsvFile -and !$SettingsObject)    {

                if ($VnetCsvFile) {
                    Write-Verbose "Vnet CSV Found."
                    $vnetObjectFile = Import-Csv -Path $VnetCsvFile

                    if ($vnetObjectFile.count -eq 1) {
                        $vnetFile = @($vnetObjectFile)
                    }
                    else {
                        $vnetFile = $vnetObjectFile
                    }
                }
                else {
                    Write-Verbose "Vnet CSV not found."
                    $vnetFile = @()
                }

                if ($RouteTableCsvFile) {
                    Write-Verbose "Route Table CSV Found."
                    $routeTablesFile = Import-Csv -Path $RouteTableCsvFile
                }
                else {
                    Write-Verbose "Route Table CSV not Found."
                    $routeTablesFile = @()
                }

                if ($NsgCsvFile) {
                    Write-Verbose "Nsg CSV Found."
                    $nsgFile = Import-Csv -Path $NsgCsvFile
                }
                else {
                    Write-Verbose "Nsg CSV Not Found."
                    $nsgFile = @()
                }

                Write-Verbose "Starting file merge."
                [System.Collections.ArrayList]$mergedFile = $vnetFile + $nsgFile + $routeTablesFile

                if ($nsgFile) {
                    $nsgCsv = $mergedFile | Group-Object nsgName

                    foreach ($nsg in ($nsgCsv | Where-Object { $_.name -like "*.csv" } )) {
                        Write-Verbose "NSG: external CSV detected"

                        foreach ($externalNsg in $nsg.Group) {
                            $interimNsgCsvPath = "$(Split-Path $NsgCsvFile)/$($nsg.Name)"
                            $interimNsgCsvPath | Write-Verbose
                            $interimNsgFile = Import-Csv -Path $interimNsgCsvPath
                            $interimNsg = $interimNsgFile | Group-Object nsgName
                            $interimNsgObjectArray = [System.Collections.ArrayList]@()

                            foreach ($internalNsg in $interimNsg.Group) {
                                $interimNsgObject = New-Object -TypeName psobject -Property @{
                                    "resourceGroupName" =  $internalNsg.resourceGroupName;
                                    "nsgName" = $internalNsg.nsgName;
                                    "ruleName" = $externalNsg.ruleName;
                                    "priority" = $externalNsg.priority;
                                    "direction" = $externalNsg.direction;
                                    "sourceIp" = $externalNsg.sourceIp;
                                    "sourcePort" = $externalNsg.sourcePort;
                                    "destinationIp" = $externalNsg.destinationIp;
                                    "destinationPort" = $externalNsg.destinationPort;
                                    "protocol" = $externalNsg.protocol;
                                    "Access" = $externalNsg.Access;
                                    "Description" = $externalNsg.Description
                                }
                                $interimNsgObjectArray.add($interimNsgObject) > $Null
                            }
                            Write-Verbose "starting merge"
                            $mergedFile.Remove($externalNsg)
                            $mergedFile += $interimNsgObjectArray
                        }
                    }
                }

                $ResourceGroupsFile = $mergedFile | Group-Object -Property resourceGroupName
                $resourceGroupObjectArray = [System.Collections.ArrayList]@()

                # Code to add vnet in the object
                function subnetObject {
                    param(
                        [Object]$subnetGroup,
                        [String]$subnetName
                    )

                    $subnetObject = @{
                        subnetName           = $subnetName;
                        cidr                 = $subnetGroup.cidr;
                        networkSecurityGroup = $subnetGroup.networkSecurityGroup;
                        routeTable           = $subnetGroup.routeTable;
                    }
                    $subnetObject
                }

                function vnetObject {
                    param(
                        [Object]$vnetGroup,
                        [String]$vnetName
                    )

                    $subnetObjectList = [System.Collections.ArrayList]@()
                    $subnetGroup = $vnetGroup | Group-Object subnetName

                    foreach ($subnet in $subnetGroup) {
                        $subnetObject = subnetObject -subnetGroup $subnet.Group -subnetName $subnet.Name
                        $subnetObjectList.Add($subnetObject) > $Null
                    }

                    if ($vnetGroup.addressSpace.count -gt 1) {
                        $addressSpace = $vnetGroup.addressSpace[0].ToString().split(',')
                    }
                    else {
                        $addressSpace = $vnetGroup.addressSpace.ToString().split(',')
                    }

                    if ($vnetGroup.Location.count -gt 1) {
                        $Location = $vnetGroup.Location[0]
                    }
                    else {
                        $Location = $vnetGroup.Location
                    }

                    $vnetObject = @{
                        vnetName     = $vnetName;
                        addressSpace = $addressSpace;
                        subnets      = $subnetObjectList;
                        Location     = $Location
                    }

                    $vnetObject
                }

                # Code to add UDR in the object
                function routeObject {
                    param(
                        [Object]$routeGroup,
                        [String]$routeName
                    )

                    $routeObject = @{
                        routeName        = $routeName;
                        cidr             = $routeGroup.cidr;
                        nextHopType      = $routeGroup.nextHopType;
                        nextHopIpAddress = $routeGroup.nextHopIpAddress
                    }

                    $routeObject
                }
                function routeTableObject {
                    param(
                        [Object]$routeTableGroup,
                        [String]$tableName
                    )

                    $routeObjectList = [System.Collections.ArrayList]@()
                    $routeGroup = $routeTableGroup | Group-Object routeName

                    foreach ($route in $routeGroup) {
                        $routeObject = routeObject -routeGroup $route.Group -routeName $route.Name
                        $routeObjectList.Add($routeObject) > $Null
                    }

                    $routeTableObject = @{
                        tableName = $tableName;
                        routes    = $routeObjectList;
                    }

                    $routeTableObject
                }
                # Code to add Nsg rules in the object
                function nsgruleObject {
                    param(
                        [Object]$nsgGroupObject
                    )

                    $nsgruleObject = @{
                        ruleName        = $nsgGroupObject.ruleName;
                        Description     = $nsgGroupObject.Description;
                        priority        = $nsgGroupObject.priority;
                        direction       = $nsgGroupObject.direction;
                        sourceIp        = $nsgGroupObject.sourceIp.ToString().split(',');
                        sourcePort      = $nsgGroupObject.sourcePort.ToString().split(',');
                        destinationIp   = $nsgGroupObject.destinationIp.ToString().split(',');
                        destinationPort = $nsgGroupObject.destinationPort.ToString().split(',');
                        protocol        = $nsgGroupObject.protocol;
                        Access          = $nsgGroupObject.Access
                    }

                    $nsgruleObject
                }

                function nsgObject {
                    param(
                        [Object]$nsgGroup,
                        [String]$nsgName
                    )

                    $nsgruleObjectList = [System.Collections.ArrayList]@()
                    $nsgGroupObject = $nsgGroup | Group-Object ruleName

                    foreach ($nsg in $nsgGroupObject) {

                        f ($nsg.count -gt 1) {
                            Write-Error "Rule name not unique : '$($nsg.Name) in '$($nsg.Group.nsgName[0])' for Resource Group: '$($nsg.Group.resourceGroupName[0])'" -CategoryTargetName $nsg.Name -ErrorAction Stop
                        }
                        $nsgruleObject = nsgruleObject -nsgGroupObject $nsg.Group
                        $nsgruleObjectList.Add($nsgruleObject) > $Null
                    }

                    $nsgObject = @{
                        nsgName = $nsgName;
                        rules   = $nsgruleObjectList;
                    }

                    $nsgObject
                }

                # Create a unified Resource Group collection
                foreach ($ResourceGroup in ($ResourceGroupsFile | Where-Object { $_.Name -notlike ''})) {

                    if ($ResourceGroup.Name) {
                        # Adding Vnets
                        Write-Verbose "working for $($ResourceGroup.Name)"
                        $vnetsGroup = $ResourceGroup.Group | Group-Object vnetName

                        Write-Verbose "'$($ResourceGroup.Name)' has vnets = '$($($vnetsGroup.Name -notlike '').count)'"
                        $vnetObjectArray = [System.Collections.ArrayList]@()

                        foreach ($vnet in $vnetsGroup) {

                            if ($vnet.name) {
                                $vnetObject = vnetObject -vnetGroup $vnet.Group -vnetName $vnet.Name
                                Write-Verbose "Adding vnet = '$($vnet.Name)' to RG = '$($ResourceGroup.Name)'"
                                $vnetObjectArray.Add($vnetObject) > $Null
                            }
                        }

                        # Adding UDR
                        $routeTableGroup = $ResourceGroup.Group | Group-Object tableName

                        Write-Verbose "'$($ResourceGroup.Name)' has route tables = '$($($routeTableGroup.Name -notlike '').count)'"
                        $routeTableObjectArray = [System.Collections.ArrayList]@()

                        foreach ($routeTable in $routeTableGroup) {

                            if ($routeTable.name) {
                                $routeTableObject = routeTableObject -routeTableGroup $routeTable.Group -tableName $routeTable.Name
                                Write-Verbose "Adding UDR = '$($routeTable.Name)' to RG = '$($ResourceGroup.Name)'"
                                $routeTableObjectArray.Add($routeTableObject) > $Null
                            }
                        }

                        # Adding NSG
                        $nsgGroup = $ResourceGroup.Group | Group-Object nsgName
                        Write-Verbose "'$($ResourceGroup.Name)' has route tables = '$($($nsgGroup.Name -notlike '').count)'"
                        $nsgObjectArray = [System.Collections.ArrayList]@()

                        foreach ($nsg in $nsgGroup) {

                            if ($nsg.name) {
                                $nsgObject = nsgObject -nsgGroup $nsg.Group -nsgName $nsg.Name
                                Write-Verbose "Adding nsg = '$($nsg.Name)' to RG = '$($ResourceGroup.Name)'"
                                $nsgObjectArray.Add($nsgObject) > $Null
                            }
                        }

                        # Adding Objects to resourceGroup Object
                        Write-Verbose "Adding '$($ResourceGroup.Name)' to Resource Group Object List"
                        $ResourceGroupObject = @{
                            resourceGroupName     = $ResourceGroup.Name;
                            vnets                 = $vnetObjectArray;
                            routeTables           = $routeTableObjectArray;
                            networkSecurityGroups = $nsgObjectArray
                        }
                        $resourceGroupObjectArray.Add($ResourceGroupObject) > $Null
                        Write-Verbose "'$($ResourceGroup.Name)' Added"
                    }

                }
            }

            # Code to Create Object from Yml
            if ($SettingsObject) {

                function createNetworkObjectFromYml {
                    param (
                        [string]
                        $ymlObject,
                        [string]
                        $ObjectType,
                        [Boolean]
                        $hasGroups
                    )

                    if ($_.StartsWith('./')) {
                        $interimPath = "$(Split-Path $SettingsFile)/$ymlObject.yml"
                    }
                    else {
                        $interimPath = "$(Split-Path $SettingsFile)/$ObjectType/$ymlObject.yml"
                    }

                    try {
                        $returnObject = Get-CmAzSettingsFile -Path $interimPath
                    }
                    catch {

                        try {
                            $interimPath.replace('/', '\')
                            $returnObject = Get-CmAzSettingsFile -Path $interimPath
                        }
                        catch {
                            throw "Not able to find file $ymlObject"
                        }

                    }

                    if ($hasGroups) {

                        ForEach ($object in $returnObject) {

                            if ($ObjectType -eq "networkSecurityGroups") {
                                $groupName = "ruleGroup"
                                $childObject = "rules"
                            }
                            elseif ($ObjectType -eq "routeTables") {
                                $groupName = "routeGroup"
                                $childObject = "routes"
                            }

                            if ($object.$groupName) {

                                $object.$groupName | ForEach-Object {

                                    try {
                                        $interimGroupPath = "$(Split-Path $interimPath)/groups/$_.yml"
                                        $returnObjectGroup = Get-CmAzSettingsFile -Path $interimGroupPath
                                    }
                                    catch [System.Management.Automation.RuntimeException] {

                                        try {
                                            $interimGroupPath.replace('/', '\')
                                            $returnObjectGroup = Get-CmAzSettingsFile -Path $interimGroupPath
                                        }
                                        catch {
                                            throw "Not able to find file at $interimGroupPath."
                                        }

                                    }

                                    $object.$childObject += $returnObjectGroup
                                }

                                $object.remove($groupName)
                            }
                        }
                    }

                    return $returnObject
                }

                $SettingsObject | ForEach-Object {
                    $vnetObjectArray = [System.Collections.ArrayList]@()
                    $routeTableObjectArray = [System.Collections.ArrayList]@()
                    $nsgObjectArray = [System.Collections.ArrayList]@()

                    # Set Vnet object
                    $_.vnets | ForEach-Object {
                        $vnetObjectYml = createNetworkObjectFromYml -ymlObject $_ -ObjectType "vnets" -hasGroups $false
                        $vnetObjectArray += $vnetObjectYml
                    }

                    # Set Route Table Object
                    $_.routeTables | ForEach-Object {
                        $routeTableObjectYml = createNetworkObjectFromYml -ymlObject $_ -ObjectType "routeTables" -hasGroups $true
                        $routeTableObjectArray += $routeTableObjectYml
                    }

                    # Set network Security group Object
                    $_.networkSecurityGroups | ForEach-Object {
                        $nsgObjectYml = createNetworkObjectFromYml -ymlObject $_ -ObjectType "networkSecurityGroups" -hasGroups $true
                        $nsgObjectArray += $nsgObjectYml
                    }

                    # Adding Objects to resourceGroup Object
                    Write-Verbose "Adding '$($_.ResourceGroupName)' to Resource Group Object List"
                    $ResourceGroupObject = @{
                        resourceGroupName     = $_.ResourceGroupName;
                        vnets                 = $vnetObjectArray;
                        routeTables           = $routeTableObjectArray;
                        networkSecurityGroups = $nsgObjectArray
                    }

                    $resourceGroupObjectArray.Add($ResourceGroupObject) > $Null
                    Write-Verbose "'$($ResourceGroup.Name)' Added"
                }
            }

            # Arm Deployment
            if ($SettingsObject -and -not $resourceGroupObjectArray) {
                $resourceGroupObjectArray = $SettingsObject.ResourceGroups.clone()
            }

            Write-Verbose "Initiating deployment"
            Write-Verbose "Setting environment Variables"

            $context = ""

            try {
                $env:PSScriptRoot = $PSScriptRoot
                $env:context = "context.json"
            }
            catch {
                write-verbose "Error setting environment variables. Make sure CmAzContext is set."
                $PSItem.ToString() | write-verbose
            }

            Save-Azcontext -Path $env:context -Force

            try {

                $resourceGroupObjectArray | ForEach-Object -Parallel {

                    Write-Verbose "Importing context.."
                    Import-Azcontext -Path $env:context > $null

                    if ($_.resourceGroupName) {
                        $ifResourceGroupExists = Get-AzResourceGroup -Name $_.resourceGroupName -ErrorAction SilentlyContinue

                        if (!$ifResourceGroupExists) {
                            New-AzResourceGroup -Name $_.resourceGroupName -Location "UK South" -Force
                        }

                        if (!$_.vnets) {
                            $_.vnets = @(@{vnetName = "none"; location = "uksouth"; addressSpace = @("10.10.0.0/24"); subnets = @(@{subnetName = "none"; cidr = "0.0.0.0/0" }) })
                        }

                        if (!$_.routeTables) {
                            $_.routeTables = @(@{tableName = "none"; routes = @(@{routeName = "none"; cidr = "0.0.0.0/0"; nextHopType = "VirtualAppliance"; nextHopIpAddress = "10.10.10.10" }) })
                        }

                        if (!$_.networkSecurityGroups) {
                            $_.networkSecurityGroups = @(@{nsgName = "none"; rules = @(@{ruleName = "none"; description = "none"; priority = "none"; direction = "none"; sourceIp = "10.10.10.10"; sourcePort = 3389; destinationIp = "10.10.10.11"; destinationPort = 3389; protocol = "Tcp"; Access = "allow" }) })
                        }

                        Write-Verbose "Starting Deployment in Resourcegroup:'$($_.resourceGroupName)"

                        New-AzResourceGroupDeployment `
                            -Name "CmAz-Network-$((Get-Date -Format "yyyymmdd-hhmmss"))" `
                            -TemplateFile "$env:PSScriptRoot\New-CmAzIaasNetworking.json" `
                            -ResourceGroupName $_.resourceGroupName `
                            -VnetArmObject $_.vnets `
                            -RouteTableArmObject $_.routeTables `
                            -NsgArmObject $_.networkSecurityGroups `
                            -Force `
                            -verbose
                    }
                }
            }
            catch {
                $PSItem.ToString() | Write-Error
            }

            Write-Verbose "Clearing environment.."
            Remove-Item -Path $env:context

            Write-Verbose "Finished."
        }
    }
    catch {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}