<#
.SYNOPSIS
    A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
    A longer description of the function, its purpose, common use cases, etc.
.NOTES
    Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
    Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
    $NewITDWindowsVmVMwareParams = @{
        ComputerName = 'itdzmtest300.nd.gov';
        CPU = 2;
        MemoryGB = 8;
        DiskOsGB = 50;
        DiskSwapGB = 9;
        DiskDataGB = 20;
        Subnet = '10.11.12.0/23';
        OS = 'Windows Server 2022 Datacenter';
        Environment = "Test";
        Datacenter = "Bismarck";
        AppName = "ITD-POC-zmeier";
        StartupPriority = 4;
        LicensingRestrictions = "No Licensing Restrictions";
        Credential = $PrvCred;
    }

    New-ITDWindowsVmVMware @NewITDWindowsVmVMwareParams
#>

function New-ITDWindowsVmVMware {
    [CmdletBinding()]
    param (
        [string]
        $ComputerName,

        [int]
        $CPU,
    
        [int]
        $MemoryGB,

        [int]
        $DiskOsGB,

        [int]
        $DiskSwapGB,

        [int]
        $DiskDataGB,

        [string]
        $Subnet,

        [string]
        $OS,

        [string]
        $Environment,
    
        [string]
        $Datacenter,

        [string]
        $AppName,

        [int]
        $StartupPriority,

        [string]
        $LicensingRestrictions,

        [PSCredential]
        $Credential

    )
    
    begin {
        
    }
    
    process {
        $ComputerName = $ComputerName.ToLower()
        $FQDN = $ComputerName
        $HostName = $FQDN.split('.')[0]

        If ($Environment -eq "Development") {
            $Environment = "Test"
        }

        Write-Verbose -Message "Prepare Credentials and Connections"
        $RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))

        Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
        Write-Verbose -Message "ComputerName: $ComputerName" -Verbose
        Write-Verbose -Message "FQDN: $FQDN" -Verbose
        Write-Verbose -Message "Hostname: $Hostname" -Verbose

        Clear-DnsClientCache
        $InfobloxVlanMetadata = Get-ITDIbVlan -CIDR $Subnet -Credential $RadiusCred
        $Cidr = ($InfobloxVlanMetadata.AssignedTo | Out-String).TrimEnd()
        [Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
        #######
        #######  Remove 10.10.10.10 references when DNS sync is fixed
        #######
        [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction SilentlyContinue -Server 10.10.10.10).IPAddress
        $SubnetMaskInt = $CIDR.split('/')[1]
        $Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
        [Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
            ([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
            ([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
            ([math]::Truncate($Int64 % 256)).ToString()
        $IPSplit = $Subnet.Split('.')
        [Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
    
        If ($null -ne $IpAddress) {
            If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
                Write-Verbose -Message "DNS record already exists, CIDR Block match"
            }
            Else {
                Write-Error "DNS record already exists, but does not match CIDR Block"
                Break
            }
        }
        Else {
            Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
            New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
            Start-Sleep -Seconds 5
            Write-Verbose -Message ("FQDN is " + $FQDN)
            #######
            #######  Review this code after DNS problems resolved - 2024/09/24 zm
            #######
            #######  [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress

            [Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
            
            If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
                Write-Error "IP Address already in use." -ErrorAction Stop
            }
        }

        Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
        If ($FQDN -like "itdcnd*") {
            $PasswordStateList = "Peoplesoft Share PW"
        }
        Else {
            $PasswordStateList = "CSRC"
        }

        $GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue
        If ($GuestVMLocalCredential) {
            Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
        }
        Else {
            Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
            $GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
        }
        $GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
        $GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))

        Write-Verbose -Message "Decide VMware Cluster and Licensing"
        switch ($LicensingRestrictions) {    
            "No Licensing Restrictions" { $ClusterRoot = "WINDOWS" }
            "Microsoft SharePoint Server" { $ClusterRoot = "WINDOWS" }
            "Microsoft SharePoint Server (Academic)" { $ClusterRoot = "WINDOWS" }
            "Microsoft SQL Developer" { $ClusterRoot = "WINDOWS" }
            "Microsoft SQL MSDN" { $ClusterRoot = "WINDOWS" }
            "Microsoft SQL Standard" { $ClusterRoot = "WINDOWS" }
            "Microsoft SQL Standard (Academic)" { $ClusterRoot = "WINDOWS" }
            "Microsoft SQL Standard (Vendor Provided)" { $ClusterRoot = "WINDOWS" }
            "Microsoft SQL Enterprise" { $ClusterRoot = "SQLe" }
            "Microsoft SQL Enterprise (Academic)" { $ClusterRoot = "SQLa" }
            "IBM Websphere" { $ClusterRoot = "WAS" }
            "Powerschool" { $ClusterRoot = "PS" }
            "Pexip" { $ClusterRoot = "TEL" }
        }

        Write-Verbose -Message "Decide Datacenter"
        switch ($Datacenter) {
            "Bismarck" { $ClusterInt = 1 }
            "Mandan" { $ClusterInt = 2 }
        }
        
        $Cluster = $ClusterRoot + $ClusterInt
        Write-Verbose -Message "VMware Cluster is $Cluster, gathering metadata for $Cluster"
        
        switch ($Cluster) {
            "WINDOWS1" {
                $ViServer = 'itdvmvc1.nd.gov'
                $ComputeCluster = Get-Cluster WINDOWS1
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
        
                If ($LicensingRestrictions -like "*SQL*") {
                    $DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS1_FS92_SQL"
                    $DiskStorageFormat = 'EagerZeroedThick'
                }
                Else {
                    $DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS1_FS92_Gen"
                    $DiskStorageFormat = 'Thin'
                }
            }
            "WINDOWS2" {
                $ViServer = 'itdvmvc2.nd.gov'
                $ComputeCluster = Get-Cluster WINDOWS2
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
                $DiskStorageFormat = 'Thin'
                If ($LicensingRestrictions -like "*SQL*") {
                    $DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS2_FS92_SQL"
                    $DiskStorageFormat = 'EagerZeroedThick'
                }
                Else {
                    $DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS2_FS92_Gen"
                    $DiskStorageFormat = 'Thin'
                }
            }
            "SQLa1" {
                $ViServer = 'itdvmvc1.nd.gov'
                $ComputeCluster = Get-Cluster SQLa1
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
                $DiskStorageFormat = 'EagerZeroedThick'
                $DatastoreCluster = Get-DatastoreCluster -Name "SQLa1_FS92_Gen"
            }
            "SQLa2" {
                $ViServer = 'itdvmvc2.nd.gov'
                $ComputeCluster = Get-Cluster SQLa2
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
                $DiskStorageFormat = 'EagerZeroedThick'
                $DatastoreCluster = Get-DatastoreCluster -Name "SQLa2_FS92_Gen"
            }
            "SQLe1" {
                $ViServer = 'itdvmvc1.nd.gov'
                $ComputeCluster = Get-Cluster SQLe1
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
                $DiskStorageFormat = 'EagerZeroedThick'
                $DatastoreCluster = Get-DatastoreCluster -Name "SQLe1_FS92_Gen"
            }
            "SQLe2" {
                $ViServer = 'itdvmvc2.nd.gov'
                $ComputeCluster = Get-Cluster SQLe2
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
                $DiskStorageFormat = 'EagerZeroedThick'
                $DatastoreCluster = Get-DatastoreCluster -Name "SQLe2_FS92_Gen"
            }
            "WAS1" {
                $ViServer = 'itdvmvc1.nd.gov'
                $ComputeCluster = Get-Cluster WAS1
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
                $DiskStorageFormat = 'Thin'
                $DatastoreCluster = Get-DatastoreCluster -Name "WAS1_FS92_Gen"
            }
            "WAS2" {
                $ViServer = 'itdvmvc2.nd.gov'
                $ComputeCluster = Get-Cluster WAS2
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
                $DiskStorageFormat = 'Thin'
                $DatastoreCluster = Get-DatastoreCluster -Name "WAS2_FS92_Gen"
            }
            "PS1" {
                $ViServer = 'itdvmvc1.nd.gov'
                $ComputeCluster = Get-Cluster PS1
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
                $DiskStorageFormat = 'Thin'
                $DatastoreCluster = Get-DatastoreCluster -Name "PS1_FS92_Gen"
            }
            "PS2" {
                $ViServer = 'itdvmvc2.nd.gov'
                $ComputeCluster = Get-Cluster PS2
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
                $DiskStorageFormat = 'Thin'
                $DatastoreCluster = Get-DatastoreCluster -Name "PS2_FS92_Gen"
            }
            "TEL1" {
                $ViServer = 'itdvmvc1.nd.gov'
                $ComputeCluster = Get-Cluster TEL1
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-TEL1-Data"
                $DiskStorageFormat = 'Thin'
                $DatastoreCluster = Get-DatastoreCluster -Name "TEL1_FS92_Gen"
            }
            "TEL2" {
                $ViServer = 'itdvmvc2.nd.gov'
                $ComputeCluster = Get-Cluster TEL2
                $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-TEL2-Data"
                $DiskStorageFormat = 'Thin'
                $DatastoreCluster = Get-DatastoreCluster -Name "TEL2_FS92_Gen"
            }
            Default {
                Write-Error "Cluster not found" -ErrorAction Stop
            }
        }

        Write-Verbose -Message "Validate entered disk sizes"
        Write-Verbose -Message "DiskOsGB is $DiskOsGB.  Validating its not a stupid number." -Verbose
        switch ($DiskOsGB) {
            { $_ -lt 50 } {
                Write-Verbose -Message "DiskOsGB is 0 or below.  Since an OS Disk is required, defaulting to 50GB" -Verbose
                $DiskOsGB = 50
            }
        }

        Write-Verbose -Message "DiskSwapGB is $DiskSwapGB.  Validating its not a stupid number." -Verbose
        switch ($DiskSwapGB) {
            { $_ -le 0 } {
                Write-Verbose -Message "DiskSwapGB is zero or below.  Since an Swap Disk is required, defaulting to `$Memory + 1GB." -Verbose
                $DiskSwapGB = ($MemoryGB + 1)
            }
        }

        Write-Verbose -Message "DiskDataGB is $DiskDataGB.  Validating its not a stupid number." -Verbose
        switch ($DiskDataGB) {
            { $_ -le 0 } {
                Write-Verbose -Message "DiskDataGB is 0.  Since an Data Disk is optional, data disk will not be created." -Verbose
                $DiskDataGB = 0
            }
            { $_ -gt 500 } {
                Write-Verbose -Message "DiskDataGB is 500GB or more.  DiskDataGB will be set to the maximum of 500GB." -Verbose
                $DiskDataGB = 500
            }
        }

        Write-Verbose -Message "Determine Datastore / Datastore Cluster"
        $DiskTotal = $DiskOsGB + $DiskSwapGB + $DiskDataGB
        If ($DatastoreCluster) {
        }
        Else {
            $DatastoreCluster = Get-DatastoreCluster | Where-Object Name -Like ("*" + $ComputeCluster + "*")
        }
        $ClusterDatastoreWithHighestFreeSpaceGB = ($DatastoreCluster | Get-Datastore | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1)
        If ($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB -gt $DiskTotal) {
            Write-Warning ("VM DiskTotal " + $DiskTotal + "GB, will fit on " + $ClusterDatastoreWithHighestFreeSpaceGB.Name + " (" + [math]::round($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB, 0) + "GB free)")
        }
        else {
            Write-Warning ("VM DiskTotal " + $DiskTotal + "GB, will not fit on " + $ClusterDatastoreWithHighestFreeSpaceGB.Name + " (" + [math]::round($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB, 0) + "GB free)")
            Write-Error ("New VM " + $FQDN + " needs " + $DiskTotal + "GB of free space on a single datastore in the " + $DatastoreCluster.Name + " datastore cluster.") -ErrorAction Stop
        }

        $FolderLocation = $ComputeCluster | Get-Datacenter | Get-Folder -Name "_New Builds"

        Write-Verbose -Message "Determine VMware VM Template for OS: $OS"
        switch ($OS) {
            "Windows Server 2012R2 Standard" { $Template = "Windows Server 2012R2 Standard" }
            "Windows Server 2016 Standard" { $Template = "Windows Server 2016 Standard" }
            "Windows Server 2019 Standard" { $Template = "Windows Server 2019 Standard 1809.19" }
            "Windows Server 2019 Datacenter" { $Template = "Windows Server 2019 Standard 1809.19" }
            "Windows Server 2022 Datacenter" { $Template = "Windows Server 2022 Standard 2108.21" }
            Default { Write-Error "Invalid template" -ErrorAction Stop }
        }

        Write-Verbose -Message "Determine VMware Port Group"
        $PortGroupsAvailable = Get-VDPortgroup -Server $ViServer -VDSwitch $VirtualSwitch
        $PortGroup = $PortGroupsAvailable | Where-Object Name -Like ("dvPG_*" + $NetworkId.IPAddressToString + "_" + $SubnetMaskInt)
        If (!($PortGroup)) {
            Write-Error "Virtual port group not found" -ErrorAction Stop
            Stop 
        }
        If (@($PortGroup).count -gt 1) {
            Write-Error "Multiple port groups found" -ErrorAction Stop
            Stop
        }

        Write-Verbose -Message "Configure Guest OS Customization Spec"
        $NewOSSpecName = ("AutoBuild-$Hostname-" + (Get-Date -UFormat "%Y%m%d%H%M%S"))
        Write-Warning "NewOSSpecName = $NewOSSpecName"
    
        Get-OSCustomizationSpec -Name 'Windows (Auto)' -Server $ViServer | New-OSCustomizationSpec -Name $NewOSSpecName -Type Persistent -Server $ViServer

        Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer | `
            Set-OSCustomizationSpec `
            -NamingScheme fixed `
            -NamingPrefix $Hostname `
            -AdminPassword $GuestCredentialBB.GetNetworkCredential().Password

        Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer | `
            Get-OSCustomizationNicMapping | `
            Set-OSCustomizationNicMapping `
            -IpMode UseStaticIP `
            -IpAddress $IpAddress.IPAddressToString `
            -SubnetMask $SubnetMask.IPAddressToString `
            -DefaultGateway $DefaultGateway.IPAddressToString `
            -Dns "10.2.7.40", "10.10.10.10"
    
        $OSSpec = Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer

        Set-Location C:\Temp
        $NewVMParams = @{
            Name                = $FQDN;
            ResourcePool        = $ComputeCluster.Name;
            Datastore           = $DatastoreCluster;
            DiskStorageFormat   = $DiskStorageFormat;
            Template            = $Template;
            Location            = $FolderLocation;
            OSCustomizationSpec = $OSSpec
        }
        try {
            New-VM @NewVMParams
        }
        catch {
            Write-Error $Error[0]
            Exit
        }

        $VM = Get-VM -Name $FQDN
    
        # Ensure CPU/Memory Hot-Add Enabled
        $vmView = $VM | Get-View
        $vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
        $vmOptValCPU = New-Object VMware.Vim.OptionValue
        $vmOptValMem = New-Object VMware.Vim.OptionValue
        $vmOptValCPU.Key = "vcpu.hotadd"
        $vmOptValMem.Key = "mem.hotadd"
        $vmOptValCPU.Value = "true"
        $vmOptValMem.Value = "true"
        $vmConfigSpec.ExtraConfig += $vmOptValCPU
        $vmConfigSpec.ExtraConfig += $vmOptValMem
        $vmView.ReconfigVM($vmConfigSpec)

        # Set CPU, Memory, Network
        $VM | Set-VM -NumCpu $CPU -MemoryGB $MemoryGB -Confirm:$false
        $VM | Get-NetworkAdapter | Set-NetworkAdapter -Portgroup $PortGroup -Confirm:$false

        # Power On VM
        $VM | Start-VM 

        # Wait for Customization to finish
        $VMStarted = $false
        $VMCustomizationStarted = $false
        $VMCustomizationResult = $false

        While ($VMStarted -eq $false -or $VMCustomizationStarted -eq $false -or $VMCustomizationResult -eq $false) {
            Write-Warning ("Customization wait loop started " + (Get-Date))
            Write-Verbose "Current Status:"
            Write-Verbose ("VMStarted: " + $VMStarted)
            Write-Verbose ("VMCustomizationStarted: " + $VMCustomizationStarted)
            Write-Verbose ("VMCustomizationResult: " + $VMCustomizationResult)
            $GetVIEventRuntime = Measure-Command -Expression { $VMEvents = Get-VIEvent -Entity $VM -Server $ViServer -ErrorAction SilentlyContinue } ## takes a long time to execute
            Write-Verbose ("Get-VIEvent last run time: " + $GetVIEventRuntime.TotalSeconds + " seconds")
            If ($VMStarted -eq $false) {
                If (@($VMEvents | Where-Object { $_.GetType().Name -eq "VMStartingEvent" })) {
                    $VMStarted = $true
                    Write-Warning "[$FQDN]:Virtual machine started"
                }
            }
            If ($VMCustomizationStarted -eq $false) {
                If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationStartedEvent" })) {
                    $VMCustomizationStarted = $true
                    Write-Warning "[$FQDN]:Virtual machine customization started"
                }
            }
            If ($VMCustomizationResult -eq $false) {
                If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationFailed" })) {
                    $VMCustomizationResult = $true
                    Write-Error "[$FQDN]:Virtual machine customization failed"
                    Exit
                    Exit
                }
                If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationSucceeded" })) {
                    $VMCustomizationResult = $true
                    Write-Warning "[$FQDN]:Virtual machine customization completed"
                }
            }
        }

        Write-Verbose -Message "Config and Update Disks"
        $VMDisk = $VM | Get-HardDisk
        $VMDisk1 = $VMDisk | Where-Object Name -EQ "Hard disk 1"
        $VMDisk2 = $VMDisk | Where-Object Name -EQ "Hard disk 2"
        $VMDisk3 = $VMDisk | Where-Object Name -EQ "Hard disk 3"

        Write-Verbose -Message "Update Disk 1"
        $VMDisk1 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 1"
        If ($VMDisk1.CapacityGB -lt $DiskOsGB) { 
            Set-HardDisk -HardDisk $VMDisk1 -CapacityGB $DiskOsGB -Confirm:$false
        }

        Write-Verbose -Message "Config Disk 2"
        If (!$VMDisk2) {
            $VM | New-HardDisk `
                -CapacityGB ($MemoryGB + 1) `
                -StorageFormat Thin `
                -DiskType Flat `
                -Persistence Persistent
        }
        $VMDisk2 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 2"
        If ($VMDisk2.CapacityGB -lt $DiskSwapGB) {
            Set-HardDisk -HardDisk $VMDisk2 -CapacityGB $DiskSwap -Confirm:$false
        }

        Write-Verbose -Message "Config Disk 3"
        If ($DiskDataGB -gt 0) {
            Write-Verbose -Message "$DiskDataGB greater than zero"
            If (!$VMDisk3) {
                $VM | New-HardDisk `
                    -CapacityGB $DiskDataGB `
                    -StorageFormat Thin `
                    -DiskType Flat `
                    -Persistence Persistent
            }
            $VMDisk3 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 3"
            If ($VMDisk3.CapacityGB -lt $DiskDataGB) {
                Set-HardDisk -HardDisk $VMDisk3 -CapacityGB $DiskData -Confirm:$false
            }
        }

        Write-Verbose -Message "Set vCenter Tags on VM"
        New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category AppName -Name $AppName) -Server $VIServer
        New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category DTAP -Name $Environment) -Server $VIServer
        New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category LicensingRestrictions -Name $LicensingRestrictions) -Server $VIServer
        New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category StartupPriority -Name $StartupPriority) -Server $VIServer
        # OS

        Write-Verbose -Message "Begin Post-Sysprep OS Guest Customization"
        Write-Verbose -Message "Assigning WMI Tag 000-Prod, SCCM will change it later if required"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Move DVD Drive Mount
            try {
                Write-Verbose "Create new Class"
                $Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);

                $Class["__CLASS"] = "ITD";
                $Class.Qualifiers.Add("Static", $true)
                $Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
                $Class.Properties["MyKey"].Qualifiers.Add("Key", $true)

                $Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
                $Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
                $Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)

                $Class.Put()

                Write-Verbose "Create single ITD Object"
                Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
            }
            catch {
                Throw $_
                Break
            }
        }

        Write-Verbose -Message "Checking for DVD drive, and moving it to Z:\"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            Write-Host "hello1"
            # Move DVD Drive Mount
            try {
                $dvd_letter = 'Z'
                $dvd = Get-WmiObject -Class Win32_Volume -Filter "DriveType=5" | Select-Object -First 1
                if ($dvd.Name -notmatch $dvd_letter) {
                    Write-Verbose -Message "Found DVD drive, switching to $dvd_letter`:"

                    Set-WmiInstance -InputObject $dvd -Arguments @{DriveLetter = "$dvd_letter`:" } | Out-Null

                    Write-Verbose -Message "DVD drive moved to drive letter $dvd_letter`:"
                }
                else {
                    Write-Verbose -Message "No DVD drive changes required, continuing..."
                }
            }
            catch {
                Throw $_
                Break
            }
        }

        Write-Verbose -Message "Checking for unpartitioned space on C: volume and expanding"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            Write-Host "hello1"
            # Expand C: Partition To Maximum Extent
            try {
                $cSize = ( Get-Partition -DriveLetter C ).Size
                $cMaxSize = ( Get-PartitionSupportedSize -DriveLetter C ).SizeMax

                if ($cSize -lt $cMaxSize) {
                    Write-Verbose -Message "Expanding C: from $($csize / 1GB)GB to $($cMaxSize / 1GB)GB..."

                    Resize-Partition -DriveLetter C -Size $cMaxSize

                    Write-Verbose -Message "C: expanded to $($cMaxSize / 1GB)GB."
                }
                else {
                    Write-Verbose -Message "C: is already at maximum size, continuing..."
                }
            }
            catch {
                Throw $_
                Break
            }
        }
    
        Write-Verbose -Message "Start Extra Disk(s) config"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Initialize Additional Disks
            try {
                # Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
                $Disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number

                If ($Disks) {
                    Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."

                    ForEach ($disk in $disks) {
                        if ($disk.IsOffline) {
                            Set-Disk $disk.Number -IsOffline $false
                            Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
                        }

                        if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
                        if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }

                        $diskParam = @{
                            FileSystem = 'NTFS'
                            Confirm    = $false
                        }

                        $driveLetter = [Int][Char]'D'
                        while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
                            $driveLetter++
                        }

                        $diskParam.DriveLetter = [Char]$driveLetter

                        if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
                            $diskParam.NewFileSystemLabel = 'Temporary Storage'
                        }
                        elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
                            $diskParam.NewFileSystemLabel = 'Data'
                        }
                    
                        [void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
                        [void](Format-Volume @diskParam)
                    }
                }
                Else {
                    Write-Verbose -Message "No unpartitioned disks found, continuing..." -Verbose
                }
            }
            catch {
                Throw $_
                Break
            }
        }

        Write-Verbose -Message "Start Page File Configuration"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Configure Page File
            if (Get-Partition -DriveLetter D -ErrorAction SilentlyContinue) {
                Write-Verbose -Message "Setting up pagefile.sys on D:..." -Verbose

                try {
                    if (-not [IO.File]::Exists('D:\pagefile.sys')) {
                        $autoPage = Get-WmiObject -Class Win32_ComputerSystem -EnableAllPrivileges
                        $autoPage.AutomaticManagedPagefile = $false
                        [void]$autoPage.Put()

                        Write-Verbose -Message "Disabled automatic pagefile management." -Verbose

                        $pageFile = Get-WmiObject -Class Win32_PageFileSetting -EnableAllPrivileges
                        $pageFile.Delete()

                        Write-Verbose -Message "Deleted C:\pagefile.sys." -Verbose

                        Set-WmiInstance -Class Win32_PageFileSetting -Arguments @{ Name = "D:\pagefile.sys"; InitialSize = 0; MaximumSize = 0; } -EnableAllPrivileges | Out-Null

                        Write-Verbose -Message "System managed page file created on D:\pagefile.sys." -Verbose
                    }
                    else {
                        Write-Verbose -Message "Pagefile already configured on D:, continuing..." -Verbose
                    }
                }
                catch {
                    Throw $_
                    Break
                }
            }
            else {
                Write-Verbose -Message "Page file drive not found, cannot set up page file. Continuing server configuration..." -Verbose
                Write-Warning "Page file drive not found, cannot set up page file. Continuing server configuration..."
            }
        }

        Write-Verbose -Message "Enabling Remote Management"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Configure Remote Management (RDP/PoSH)
            try {
                Write-Verbose -Message "Checking WinRM..." -Verbose

                if (Test-WSMan -ErrorAction SilentlyContinue) {
                    Write-Verbose -Message "WinRM is already enabled."
                }
                else {
                    Enable-PSRemoting -Force

                    Write-Verbose -Message "WinRM is now enabled."
                }

                Write-Verbose -Message "Checking RDP..." -Verbose

                $RDP = Get-WmiObject Win32_TerminalServiceSetting -Namespace root\cimv2\TerminalServices
                $NLA = Get-WmiObject Win32_TSGeneralSetting -Namespace root\cimv2\TerminalServices -Filter "TerminalName='RDP-tcp'"
                if ($RDP.AllowTSConnections -eq 0) {
                    Write-Verbose -Message "RDP is disabled, enabling..."

                    $RDP.SetAllowTSConnections(1, 1) | Out-Null

                    Write-Verbose -Message "RDP is enabled."
                }
                else {
                    Write-Verbose -Message "RDP is already enabled, checking NLA security..."
                }

                if ($NLA.UserAuthenticationRequired -eq 0) {
                    Write-Verbose -Message "RDP is not NLA secured, enabling..."

                    $NLA.SetUserAuthenticationRequired(1) | Out-Null

                    Write-Verbose -Message "RDP is now NLA secured."
                }
                else {
                    Write-Verbose -Message "RDP is already NLA secured."
                }

            }
            catch {
                Throw $_
                Break
            }
        }

        Write-Verbose -Message "Checking current power plan, set to High Performance"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Configure Power Plan
            try {
                $powerPlans = powercfg -l

                if ($powerPlans -match '\*$' -notmatch 'High performance') {
                    $currentPlan = [regex]::Match($powerPlans, '(?<=(\())[^)]+(?=(\)\s\*))').Value

                    Write-Verbose -Message "Power plan is currently set to $currentPlan, changing to High Performance..."  -Verbose

                    $highPerformance = [regex]::Match($powerPlans, '([\d\w-\S]+)(?=\s+\(High performance\))').Value
                    [void](powercfg -setactive $highPerformance)

                    Write-Verbose -Message "Power plan set to High Performance." -Verbose
                }
                else {
                    Write-Verbose -Message "Power plan already configured for High Performance." -Verbose
                }

                [void](& w32tm.exe /resync /nowait)
            }
            catch {
                Throw $_
                Break
            }
        }
    
    
        $TimeSyncFunc = {
            # Configure Time/Date Settings
            Write-Verbose -Message "Checking current time/date settings..."
            $Domain = "<DomainName>"
            try {
                if ((Get-TimeZone).Id -ne 'Central Standard Time') {
                    Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time." -Verbose

                    Set-TimeZone -Id 'Central Standard Time'

                    Write-Verbose -Message "Time zone set to Central Standard Time." -Verbose
                }
                else {
                    Write-Verbose -Message "Time zone is already set to Central Standard Time." -Verbose
                }
            }
            catch {
                Throw $_
                Break
            }
        }
        $TimeSyncScriptBlock = $TimeSyncFunc -replace '<DomainName>', $DomainName
        $VM | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText  $TimeSyncScriptBlock
        #>
        Write-Verbose -Message "Enable the server manager performance monitors"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Enable Performance Counters
        

            try {
                if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
                    Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask

                    Write-Verbose -Message "Performance monitors enabled." -Verbose
                }
                else {
                    Write-Verbose -Message "Performance monitors already enabled, continuing..." -Verbose
                }
            }
            catch {
                Throw $_
                Break
            }
        }

        Write-Verbose -Message "Disable Windows Firewall"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
            # Disable Windows Firewall
            Write-Verbose -Message "Checking for active Windows Firewall..." -Verbose

            if ((Get-NetFirewallProfile).Enabled -contains 'True') {
                Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."

                Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False

                Write-Verbose -Message "Windows Firewall disabled." -Verbose
            }
            else {
                Write-Verbose -Message "Windows Firewall already disabled, continuing..." -Verbose
            }
        }

        # Active Directory
        Write-Verbose "Join Active Directory"
        $DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
        switch ($DomainName) {
            'nd.gov' {
                $SearchBaseDomain = "dc=nd,dc=gov"
            }
            'ndcloud.gov' {
                $SearchBaseDomain = "dc=ndcloud,dc=gov"
            }
        }

        If ($DomainName -eq "nd.gov") {
            $OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
            If (!($OUAppName)) {
                $OUAppName = Get-ADOrganizationalUnit -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' }
            }
            $ExistingADComputer = Get-ADComputer -Filter { Name -eq $Hostname }
            If ($ExistingADComputer) {
                If ($ExistingADComputer.DistinguishedName -like ("*" + $AppName + "*") -or $ExistingADComputer.DistinguishedName -like "*All-General*") {
                    Write-Warning "AD object already exists, OU path does match"
                    $OuFinal = $ExistingADComputer.DistinguishedName -replace '^.+?(?<!\\),', ''
                }
                Else {
                    Write-Error "AD object already exists, OU path mismatch"
                    Exit
                    Exit
                }
            }
            Else {
                switch ($Environment) {
                    'Test' {
                        If ($AppName -like "Shared-Peoplesoft*") { $EnvString = "Non-Prod" }
                        Else { $EnvString = "Test" }
                        $OuAppNameEnv = Get-ADOrganizationalUnit -SearchBase $OUAppName.DistinguishedName -Filter * | Where-Object Name -EQ "$EnvString"
                    }
                    'Production' {
                        If ($AppName -like "Shared-Peoplesoft*") { $EnvString = "Prod" }
                        Else { $EnvString = "Prod" }
                        $EnvString = "Prod"
                        $OuAppNameEnv = Get-ADOrganizationalUnit -SearchBase $OUAppName.DistinguishedName -Filter * | Where-Object Name -EQ "$EnvString"
                    }
                }
                If ($OuAppNameEnv) { $OUFinal = $OUAppNameEnv.DistinguishedName }
                Else { $OuFinal = $OUAppName.DistinguishedName }

                #New-ADComputer -Name $HostName.ToUpper() -Path $OUFinal -Credential $ADCred -Description ($SPItem | Select-Object AppName, Environment | ConvertTo-Json)
            }
    
            $FirstScriptBlock = { $DomainJoinCred = New-Object System.Management.Automation.PSCredential('svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force)) }
            $SecondScriptText = 'Add-Computer -DomainName <DomainName> -OUPath "<OuPath>" -Credential $DomainJoinCred -Server itddc12.nd.gov'
            $SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
            $SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
            $SecondScriptText = $SecondScriptText -replace '<OuPath>', ("OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain)

            Write-Warning -Message "[$FQDN]:Invoke-VMScript to AD join"
            $InvokeVMScriptFunc = [System.Management.Automation.ScriptBlock]::Create("$FirstScriptBlock ; $SecondScriptText")

            Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText $InvokeVMScriptFunc
        
            Write-Warning -Message "[$FQDN]:Restart VMGuest, wait for Tools, then 90 seconds after"
            Get-VM -Name $FQDN | Restart-VMGuest -Confirm:$false
            Wait-Tools -VM (Get-VM -Name $FQDN)
            Start-Sleep -Seconds 90
        }
        else {
            $GuestCredentialAB = $GuestCredentialBB
        }

        Write-Verbose -Message ("[$FQDN]:Copying SCCM client installer to C:\temp... " + (Get-Date))
        
        Copy-VMGuestFile -Source C:\SCCM_Client\ -Destination C:\temp\SCCM_Client -VM (Get-VM -Name $FQDN) -LocalToGuest -GuestCredential $GuestCredentialAB -Force
        Write-Verbose -Message ("[$FQDN]:SCCM client copy complete " + (Get-Date))
        #E:\AutoBuild\SCCM_Client\

        # Check if SCCM automatically installed the SCCM client and registered it
        $CcmRegistered = $false
        $CcmRegistration = Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
            Get-Content C:\windows\ccm\logs\ClientIDManagerStartup.log
        }
        If ($CcmRegistration.ScriptOutput -match "Client is registered") {
            Write-Warning "Client is registered."
            $CcmRegistered = $true
        }
        ElseIf ($CcmRegistration.ScriptOutput -match "Client is already registered") {
            Write-Warning "Client is already registered."
            $CcmRegistered = $true 
        }
        If ($CcmRegistered -eq $false) {
            Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
        
                If (Get-Process -Name ccmsetup -ErrorAction SilentlyContinue) {
                    Write-Warning "CCM client is already installing"
                    $CcmRegistered = $true 
                }
                ElseIf (Get-Process -Name ccmexec -ErrorAction SilentlyContinue) {
                    Write-Warning "CCM client is already installed"
                    $CcmRegistered = $true
                }
                Else {
                    Write-Warning -Message "Installing SCCM Client..."
                    Invoke-Expression -Command "C:\temp\SCCM_Client\ccmsetup.exe SMSSITECODE=ITD SMSMP=itdsccmp2.nd.gov DNSSUFFIX=nd.gov"
                }
            }
        }

        Write-Verbose -Message "[$FQDN]:Register SCCM Client"
        While ($CcmRegistered -eq $false) {
            $CcmRegistration = Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
                Get-Content C:\windows\ccm\logs\ClientIDManagerStartup.log
            }
            If ($CcmRegistration.ScriptOutput -match "Client is registered") {
                Write-Warning "Client is registered."
                $CcmRegistered = $true
            }
            ElseIf ($CcmRegistration.ScriptOutput -match "Client is already registered") {
                Write-Warning "Client is already registered."
                $CcmRegistered = $true 
            }
            ElseIf ($CcmRegistered -eq $false) { Start-Sleep -Seconds 30 }
        }
    
        Write-Verbose -Message "[$FQDN]:Approve SCCM Client"
        #Start-Sleep -Seconds 30   # ADD LOOP/SMARTS TO WAIT FOR DISCOVERY AND ANOTHER FOR APPROVAL
        Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
            Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
            $PSDrives = Get-PSDrive
            If ($PSDrives | Where-Object Name -EQ "ITD") {
                # ITD Drive exists, do nothing
            }
            else {
                New-PSDrive -Name "ITD" -PSProvider AdminUI.PS.Provider\CMSite -Root itdsccmp2.nd.gov   
            }

            Set-Location ITD:\
            $Device = Get-CMDevice -Name $args[0]
            If ($Device.IsApproved -eq 0) {
                Approve-CMDevice -DeviceName $args[0]
            }
        }
    

        Write-Verbose -Message "Trigger SCCM MachinePolicy First Check-in"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
            [void] ([wmiclass] "\\localhost\root\ccm:SMS_Client").TriggerSchedule("{00000000-0000-0000-0000-000000000021}");
        }

        Write-Verbose -Message "Trigger SCCM MachinePolicy"
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
            [void] ([wmiclass] "\\localhost\root\ccm:SMS_Client").TriggerSchedule("{00000000-0000-0000-0000-000000000021}");
        }
        #Start-Sleep -Seconds 180

        Write-Verbose -Message "Waiting for network connectivity / Then KVM Activation..."
        Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
            # Pause until network connectivity is available
            $KMS = 'kms.nd.gov'

            try {
                $nwJob = Start-Job -Name 'NetworkCheck' -ScriptBlock {
                    Param ( [String]$KMS )
                    do {
                        $nwStatus = Test-NetConnection -ComputerName $KMS -Port 1688 -InformationLevel Quiet

                        Start-Sleep -Seconds 10
                    } until($nwStatus)
                } -ArgumentList $KMS

                # If after 30 seconds the network connection is not responding continue on
                if ((Wait-Job -Job $nwJob -Timeout 30).State -eq 'Completed') {
                    Write-Verbose -Message 'Network connectivity has been verified.'
                }
                else {
                    [void](Stop-Job -Job $nwJob)
                    Write-Verbose -Message 'Network connectivity could not be verified.'
                }
            }
            catch {
                Throw $_
                Break
            }

            # Activate via KMS
            Write-Verbose -Message "Activating windows against $KMS..."
            if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
                Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit
            }
            try {
                cscript C:\Windows\System32\slmgr.vbs /skms $KMS | Out-Null
                cscript C:\Windows\System32\slmgr.vbs /ato | Out-Null

                Write-Verbose -Message "Checking activation status..."

                $kmsOut = cscript C:\Windows\System32\slmgr.vbs /dli

                if (($kmsOut | Select-String -Pattern '^License Status:') -match 'Licensed') {
                    Write-Verbose -Message "Windows successfully activated."
                }
                else {
                    Write-Verbose -Message "Windows failed to activate, run slmgr commands manually. Ensure server time is correct."
                    Write-Warning -Message "Windows failed to activate, run slmgr commands manually. Ensure server time is correct."
                }
            }
            catch {
                Throw $_
                Break
            }

            Write-Verbose -Message ("[$FQDN]:Add to Solarwinds")
            $Func = {
                param($C)
                Import-SWDiscovery -ComputerName $C -Integration ServiceNow
            }
            Invoke-Command -ComputerName itdslrwnds.nd.gov -ScriptBlock $Func -ArgumentList $FQDN -Credential $Credential
        }
        Write-Verbose -Message "[$FQDN]:End"
    }
    
    end {
        
    }
}