This commit is contained in:
Zack Meier
2026-04-15 15:45:50 -05:00
commit 1d304511b8
613 changed files with 140998 additions and 0 deletions
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.ITD-WindowsServer.Lifecycle'
variables:
major: 0
minor: 1
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.Lifecycle.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.Lifecycle.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.ITD-WindowsServer.Lifecycle'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.ITD-WindowsServer.Lifecycle).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.ITD-WindowsServer.Lifecycle</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Windows Server Lifecycle administration</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,132 @@
#
# Module manifest for module 'ITD.ITD-WindowsServer.Lifecycle'
#
# Generated by: zmeier
#
# Generated on: 6/14/2022
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITD.ITD-WindowsServer.Lifecycle.psm1'
# Version number of this module.
ModuleVersion = '<ModuleVersion>'
# Supported PSEditions
CompatiblePSEditions = 'Desktop', 'Core'
# ID used to uniquely identify this module
GUID = '42b3d283-1030-4bf9-afa7-4810061f74a8'
# Author of this module
Author = 'zmeier'
# Company or vendor of this module
CompanyName = 'State of North Dakota'
# Copyright statement for this module
Copyright = '(c) zmeier. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Windows Server general administration'
# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(<FunctionsToExport>)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,23 @@
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,447 @@
<# ################
.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
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest901.nd.gov';
CPU = 2;
MemoryGB = 8;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = "Windows Server 2025 Datacenter";
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
.EXAMPLE
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest701.nd.gov';
ResourceGroupNameOverride = 'rg-shared-iis-tst';
AvailabilityZone = 2;
CPU = 2;
MemoryGB = 8;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = 'Windows Server 2022 Datacenter';
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
#>
function New-ITDWindowsVmAzureStep1 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$ResourceGroupNameOverride,
[string]
$AppName,
[ValidateSet(1, 2, 3)]
[int]
$AvailabilityZone,
[Parameter(ParameterSetName = 'VmSizeOverride')]
[string]
$VMSizeOverride,
[Parameter(ParameterSetName = 'VmSizeDetermine')]
[int]
$CPU,
[Parameter(ParameterSetName = 'VmSizeDetermine')]
[int]
$MemoryGB,
[int]
$DiskOsGB,
[int]
$DiskDataGB,
[string]
$Subnet,
[string]
$OS,
[string]
$VMEnvironment,
#[string]
#$Subscription,
[string]
$LicensingRestrictions,
[PSCredential]
$Credential
)
begin {
}
process {
$FQDN = $FQDN.ToLower()
$Hostname = $FQDN.split('.')[0]
Write-Verbose -Message "Prepare Connections"
#$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54'
#$AppId = '60244573-7130-4026-9c6d-47de73f8ca29'
#$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx
#$SecureStringPwd = 'sQV8Q~sNLcrDl2RgB32gsSEsDVgdFhNMcBdPoaEX'
#$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force)
#Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId
Write-Verbose -Message "Prepare Credentials"
$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"
Clear-DnsClientCache
$Cidr = $Subnet
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).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-Warning "DNS record already exists, CIDR Block match"
}
Else {
Write-Error "DNS record already exists, and 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)
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
#[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
If ($null -eq $GuestVMLocalCredential) {
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
}
Else {
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
}
$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 "Determine environment"
switch ($VMEnvironment) {
{ $_ -eq 'Test' -or $_ -eq 'Development' } {
Write-Verbose -Message "Environment is Test or Development"
$EnvShortString = 'tst'
}
'Production' {
Write-Verbose -Message "Environment is Production"
$EnvShortString = 'prd'
}
}
Write-Verbose -Message "Determine subscription, via Subnet"
$AllSubscriptions = Get-AzSubscription
$AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) {
Set-AzContext -SubscriptionObject $Subscription | Out-Null
Get-AzVirtualNetwork | ForEach-Object {
[PSCustomObject]@{
Subscription = $Subscription.Name;
VirtualNetwork = $_
}
}
}
$Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription
$VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork
$VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet }
$VNetName = $VirtualNetwork.Name
$VNetSubnet = $VirtualNetworkSubnet.Name
Write-Verbose -Message "OS is $OS"
switch ($OS) {
{ $_ -eq "Windows Server 2019 Datacenter" -or $_ -eq "Windows Server 2019" } {
Write-Verbose -Message "OS is Windows Server 2019"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2019-Datacenter"
$SecurityType = ''
}
{$_ -eq "Windows Server 2022 Datacenter" -or $_ -eq "Windows Server 2022" } {
Write-Verbose -Message "OS is Windows Server 2022"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "windowsserver2022"
$sku = "2022-datacenter"
$SecurityType = ''
}
{ $_ -eq "Windows Server 2025 Datacenter" -or $_ -eq "Windows Server 2025" } {
Write-Verbose -Message "OS is Windows Server 2025"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2025-datacenter"
$SecurityType = ''
}
"Windows 11 24H2" {
Write-Verbose -Message "OS is Windows 11 24H2"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsDesktop"
$Offer = "Windows-11"
$sku = "win11-24h2-ent"
$SecurityType = ''
}
Default { Write-Error "Invalid operating system" -ErrorAction Stop }
}
# finalize VM location and size
$location = "centralus"
switch ($PSCmdLet.ParameterSetName) {
"VmSizeOverride" {
Write-Verbose -Message "ParameterSet is VmSizeOverride"
$VMSize = Get-AzVMSize -Location centralus | Where-Object { $_.Name -eq $VMSizeOverride }
}
"VmSizeDetermine" {
Write-Verbose -Message "ParameterSet is VmSizeDetermine"
$VMSizeFilter1 = @(
"Standard_D2ds_v5",
"Standard_D4ds_v5",
"Standard_D8ds_v5",
"Standard_D16ds_v5",
"Standard_D32ds_v5",
"Standard_E2ds_v5",
"Standard_E4ds_v5",
"Standard_E8ds_v5",
"Standard_E16ds_v5",
"Standard_E20ds_v5",
"Standard_E32ds_v5",
"Standard_F2s_v2",
"Standard_F4s_v2",
"Standard_F8s_v2",
"Standard_F16s_v2",
"Standard_F32s_v2"
)
$VMSize = Get-AzVMSize -Location centralus | `
Where-Object { $VMSizeFilter1 -contains $_.Name } | `
Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | `
Where-Object Name -NotMatch "_Promo" | `
Sort-Object NumberOfCores, MemoryInMB | `
Select-Object -First 1
}
}
Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed"
If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) {
# use the name in the variable
Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride"
$ResourceGroupName = $ResourceGroupNameOverride
}
Else {
Write-Verbose -Message "ResourceGroupName parameter not found, determine now"
# if name is not given, determine the name
$VMOwner = $AppName.split('-')[0].ToLower()
$VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " "
$ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString"
}
Write-Verbose -Message "ResourceGroupName is $ResourceGroupName"
Write-Verbose -Message "Change to selected subscription and validate resource group exists"
Set-AzContext $Subscription | Out-Null
$ResGroupExist = $null
$ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
If (@($ResGroupExist).count -gt 1) {
Write-Error "Multiple Resource Groups matched" -ErrorAction Stop
}
If (@($ResGroupExist).count -lt 1) {
Write-Warning "No matching resource group found, creating it now"
New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName }
}
If (@($ResGroupExist).count -eq 1) {
Write-Verbose -Message "Exactly one matching resource group found"
}
$VMName = "vm-$HostName-$EnvShortString"
$VMIPConfigName = "ipconfig-$HostName-$EnvShortString"
$VMNicName = "nic-$HostName-$EnvShortString"
$VMObjectName = "vm-$HostName-$EnvShortString"
$VMOsDiskName = "vm-$HostName-os-$EnvShortString"
Write-Verbose -Message "Verifying Applicable Inputs are Lowercase"
$VMIPConfigName = $VMIPConfigName.ToLower()
$VMNicName = $VMNicName.ToLower()
$VMObjectName = $VMObjectName.ToLower()
$VMOsDiskName = $VMOSDiskName.ToLower()
Write-Verbose -Message "Creating IPConfig and NIC"
$IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet
$VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force
$VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString
Set-AzNetworkInterface -NetworkInterface $VMNIC
Write-Verbose -Message "Build VM Config"
$vmConfigParams = @{
VMName = $VMObjectName
VMSize = $VMSize.Name
}
If ($PSBoundParameters.ContainsKey('AvailabilityZone')) {
Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams"
$vmConfigParams += @{
Zone = $AvailabilityZone
}
}
$vmConfig = New-AzVMConfig @vmConfigParams
# Security Profile
switch ($SecurityType) {
'TrustedLaunch' {
Write-Verbose -Message "Security Profile is TrustedLaunch"
$vmConfig = Set-AzVMSecurityProfile -VM $vmConfig -SecurityType "TrustedLaunch"
}
'' {
Write-Verbose -Message "Security Profile is empty"
}
Default {
Write-Verbose -Message "Security Profile is null"
}
}
$vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage
If ($VMOS -eq "Windows") {
$vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential
$vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest
$vmConfig.OSProfile.ComputerName = $HostName
}
If ($VMOS -eq "Linux") {
$vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential
Switch ($VMSubscription) {
"npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" }
"infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" }
"prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" }
}
}
$vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID
Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName
Write-Verbose "Creating VM"
New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob
Write-Verbose -Message "New-AzVM completed, waiting 60 seconds before checking for completion"
Start-Sleep -Seconds 60
$VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName
If ($PSBoundParameters.ContainsKey("DiskDataGB")) {
$ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$VMEnvironment-") } })
$NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1
$NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000")
$NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001
$LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1
$count = 0
If ($ExistingDisks) {
while ($Size -match $ExistingDisks.DiskSizeGB) {
$count++
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
If ($count -ge 11) {
Write-Error "Disk size not available" -ErrorAction Stop
}
Else {
$Size = $Size - 5
}
}
}
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
If ($null -ne $DiskDataGB -and $DiskDataGB -gt 0) {
$AzureRmDiskConfigParams = @{
DiskSizeGB = $DiskDataGB
Location = $Location
CreateOption = "Empty"
SkuName = "Premium_LRS"
}
If ($AvailabilityZone) {
Write-Verbose "VM is located in Availability Zone $Zone"
$AzureRmDiskConfigParams += @{Zone = $Zone }
}
#$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS
$DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams
If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) {
$NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName
$VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly
Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob
}
}
}
Write-Verbose -Message "Add networking tag to VM" -Verbose
$Tags = @{"Network"="StandardWindows"}
New-AzTag -ResourceId $VM.Id -Tag $Tags
}
end {
}
}
@@ -0,0 +1,341 @@
function New-ITDWindowsVMAzureStep2 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$AppName,
[string]
$VMEnvironment,
[PSCredential]
$Credential
)
begin {
$search = @()
#$Hostname = $VMName.split('-')[1]
$HostName = $FQDN.split('.')[0]
###$VMEnvironment = $VMName.split('-')[2] ####
Write-Verbose -Message ("Searching for Az VM name like " + $HostName)
Get-AzSubscription | ForEach-Object {
$Subscription = $_
Set-AzContext $_ | Out-Null
$search += Get-AzVM | Where-Object Name -Like "*$HostName*" | Select-Object *, @{n = 'SubscriptionName'; e = { $Subscription.Name } }
}
}
process {
switch (@($Search).count) {
{ $_ -gt 1 } { Write-Error -Message "More than one virtual machine match found." }
{ $_ -lt 1 } { Write-Error -Message "Less than one virtual machine match found." }
{ $_ -eq 1 } {
Write-Verbose -Message "Prep VM variables"
$VMName = $search.Name
$ResourceGroupName = $search.ResourceGroupName
#$VMEnvironment = $VMName.split('-')[2]
$SubscriptionName = $search.SubscriptionName
Set-AzContext -Subscription $SubscriptionName
$VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
Write-Verbose -Message "Set firewall tag"
$Tags = @{"Network"="StandardWindows"}
New-AzTag -ResourceId $VM.Id -Tag $Tags
Write-Verbose -Message "Wait two minutes before Pre-Firewall Guest OS customization"
#Start-Sleep -Seconds 120
Write-Verbose -Message "Begin Pre-Firewall Guest OS customization"
$InvokeAzVMRunCommandParams = @{
ResourceGroupName = $ResourceGroupName;
Name = $VMName;
CommandId = 'RunPowerShellScript'
}
Write-Verbose -Message "1-Set WMI Tags"
$ScriptBlock = {
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
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "3-Disk Configuration"
$ScriptBlock = {
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."
# Prevent the "You must format this partition before using it." popup
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Stop-Service ShellHWDetection -ErrorAction SilentlyContinue }
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..."
}
}
catch {
Throw $_
Break
}
finally {
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Start-Service ShellHWDetection -ErrorAction SilentlyContinue }
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "5-Time Zone"
$ScriptBlock = {
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time."
Set-TimeZone -Id 'Central Standard Time'
Write-Verbose -Message "Time zone set to Central Standard Time."
}
else {
Write-Verbose -Message "Time zone is already set to Central Standard Time."
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "6-Enable Performance Counters"
$ScriptBlock = {
# 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."
}
else {
Write-Verbose -Message "Performance monitors already enabled, continuing..."
}
}
catch {
Throw $_
Break
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "7-Disable Windows Firewall"
$ScriptBlock = {
# Disable Windows Firewall
Write-Verbose -Message "Checking for active Windows Firewall..."
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."
}
else {
Write-Verbose -Message "Windows Firewall already disabled, continuing..."
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
# determine Active Directory Forest and OU
Write-Verbose -Message "8a-Determine domain join"
$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 ($null -eq $OUAppName) {
Write-Verbose -Message "Dedicated AppName OU for $AppName not found, placing in All-General OU"
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' }
}
Write-Verbose -Message ("OUAppName: " + $OUAppName.distinguishedName) -Verbose
# Determine if the Environment sub-ou is special for the selected AppName
switch ($OUAppName.Name) {
'Shared-PeopleSoft-HigherEd' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
'Shared-PeopleSoft-State' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
Default {
switch ($VMEnvironment) {
'Test' { $EnvString = "Test" }
'Production' { $EnvString = "Prod" }
}
}
}
# Gather all sub OUs for the AppName OU
Write-Verbose -Message "EnvString is $EnvString" -Verbose
$SubOUs = Get-ADOrganizationalUnit -Server $DomainName -SearchBase $OUAppName -Filter *
Write-Verbose -Message ("SubOUs:") -Verbose
$SubOUs | ForEach-Object { Write-Verbose -Message $_.DistinguishedName -Verbose }
$LikeFilter = ("OU=$EnvString,OU=" + $OUAppName.Name + "*")
Write-Verbose -Message "LikeFilter: $LikeFilter"
$OUToUse = $SubOUs | Where-Object { $_.DistinguishedName -like $LikeFilter }
Write-Verbose -Message ("OUToUse: " + $OUToUse.distinguishedName) -Verbose
<#$MatchFilter = ("OU=$EnvString,OU=" + $OUAppName.Name)
Write-Verbose -Message "MatchFiler: $MatchFilter"
$OutToUseMatch = ($SubOUs | Where-Object {$_.DistinguishedName -match "^$MatchFilter"})
Write-Verbose -Message ("OUToUseMatch: " + $OUToUseMatch.distinguishedName) -Verbose
#>
# Determine if AD Computer object already exists
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $HostName }
If ($ExistingADComputer) {
Write-Verbose -Message "AD Object already exists in AD"
# Determine if AD Computer object is in the correct OU, or a sub-OU of the correct OU
If ($ExistingADComputer.DistinguishedName -like ("*" + $OUToUse.DistinguishedName)) {
Write-Verbose -Message "AD Object is in the correct OU, will attempt domain join"
$AttemptDomainJoin = $true
}
Else {
Write-Verbose -Message "AD Object is NOT in the correct OU, domain join will not occur, needs human review"
$AttemptDomainJoin = $false
}
}
Else {
$AttemptDomainJoin = $true
}
If ($AttemptDomainJoin) {
$OuFinal = $OUToUse.DistinguishedName
Write-Verbose -Message "OuFinal: $OuFinal" -Verbose
$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 itddc42.nd.gov -Verbose; Restart-Computer -Force -Verbose;'
$SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
$SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
#Write-Verbose -Message "[$FQDN]:Invoke-VMScript to AD join"
$InvokeVMScriptFunc = [System.Management.Automation.ScriptBlock]::Create("$FirstScriptBlock ; $SecondScriptText")
}
Write-Verbose -Message $InvokeVMScriptFunc.tostring() -Verbose ###
<# wait for firewall and reconnect
Write-Verbose -Message "8b-Verify if firewall is open before domain join attempt"
$connectivity = $false
while ($connectivity -eq $false) {
If ( (Test-NetConnection -ComputerName $FQDN).PingSuccedded) {
Write-Verbose -Message "Ping successful" -Verbose
$connectivity = $true
}
Else {
Write-Verbose -Message "Ping failed, may need to wait for PA IP sync" -Verbose
Start-Sleep -Seconds 60
}
}
#>
Write-Verbose -Message "8c-Attempt domain join"
switch ($DomainName) {
'nd.gov' {
Write-Verbose -Message "Attempting domain join nd.gov"
Write-Verbose -Message "Domain: $DomainName"
Write-Verbose -Message "OuPath: $OuFinal"
<#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptPath 'C:\AzWinBuild\8-Domain-ndgov.ps1' -Parameter @{
"DomainName" = 'nd.gov';
"OuPath" = "$OuFinal"
}#>
$ScriptBlock = {
Param(
[string]
$DomainName,
[string]
$OuPath
)
Write-Host $OuPath
Test-NetConnection -ComputerName nd.gov
$DomainJoinCred = New-Object System.Management.Automation.PSCredential('ndgov\svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force))
Add-Computer -DomainName $DomainName -OUPath $OuPath -Credential $DomainJoinCred
Restart-Computer -Force
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock -Parameter @{
"DomainName" = 'nd.gov';
"OuPath" = "$OuFinal"
}
#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $InvokeVMScriptFunc
}
}
}
}
}
}
end {
}
}
@@ -0,0 +1,529 @@
<#
### step 1
gather specs
provision virtual machine on hypervisor
final result:
cpu configured, with hot-add
memory configured, with hot-add
disks 1, 2, and 3 attached with correct sizing
network connected
virtual machine powered on
update SCTASK Step 1 completed
### step 2 - customize Windows Guest OS
wait for SCTASK Step 1 completed message
configure
time zone, mount points, page file, etc
update SCTASK Step 2 completed
### step 3 - administration
wait for SCTASK Step 2 completed message
domain join
nd.gov at a minimum
install sccm
regardless if domain joined
update SCTASK Step 3 completed
### step 4 - validation
confirm all agents are installed
disks are present and formatted
#>
<#
.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-ITDWindowsVmVMwareStep1 {
[CmdletBinding()]
param (
[string]
$FQDN,
[int]
$CPU,
[int]
$MemoryGB,
[int]
$DiskOsGB,
[int]
$DiskSwapGB,
[int]
$DiskDataGB,
[string]
$Subnet,
[string]
$OS,
[string]
$VMEnvironment,
[string]
$Datacenter,
[string]
$AppName,
[int]
$StartupPriority,
[string]
$LicensingRestrictions,
[PSCredential]
$Credential
)
begin {
}
process {
$FQDN = $FQDN.ToLower()
$HostName = $FQDN.split('.')[0]
If ($VMEnvironment -eq "Development") {
$VMEnvironment = "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 "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."
try {
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Start-Sleep -Seconds 5
Write-Verbose -Message ("FQDN is " + $FQDN)
}
catch {
}
#######
####### 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
If ($null -eq $GuestVMLocalCredential) {
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
}
Else {
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
}
$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" { $Template = "Windows Server 2019 Standard 1809.19" }
"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" { $Template = "Windows Server 2022 Standard 2108.21" }
"Windows Server 2022 Datacenter" { $Template = "Windows Server 2022 Standard 2108.21" }
"Windows Server 2025" { $Template = "Windows Server 2025 Standard 24H2.6" }
Default { Write-Error "Invalid template option: $OS" -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;
Location = $FolderLocation;
# Removed when using Content Library
#Template = $Template;
#OSCustomizationSpec = $OSSpec
}
Get-ContentLibraryItem -Name $Template -Server $ViServer | New-VM @NewVMParams
$VM = Get-VM -Name $FQDN
$VM | Set-VM -OSCustomizationSpec $OSSpec -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 $VMEnvironment) -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
# 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
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 $DiskSwapGB -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 $DiskDataGB -Confirm:$false
}
}
Write-Verbose -Message "Set VMware Tools Upgrade Policy to UpgradeAtPowerCycle"
$VMView = $VM | Get-View
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmConfigSpec.Tools = New-Object VMware.Vim.ToolsConfigInfo
$vmConfigSpec.Tools.ToolsUpgradePolicy = "UpgradeAtPowerCycle"
$VMView.ReconfigVM($vmConfigSpec)
Write-Verbose -Message "Power On VM"
$VM | Start-VM
Write-Verbose -Message "[$FQDN]:Step 1 End"
}
end {
}
}
@@ -0,0 +1,667 @@
<#
.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';
Environment = "Test";
AppName = "ITD-POC-zmeier";
Credential = $PrvCred;
}
New-ITDWindowsVmVMware @NewITDWindowsVmVMwareParams
#>
function New-ITDWindowsVmVMwareStep2 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$AppName,
[string]
$VMEnvironment,
[PSCredential]
$Credential
)
begin {
}
process {
Write-Verbose -Message "Start New-ITDWindowsVmVMwareStep2"
$FQDN = $FQDN.ToLower()
$HostName = $FQDN.split('.')[0]
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
$VM = Get-VM -Name $FQDN
$ViServer = $VM.Uid.split('@')[1].split(':')[0]
$VMTagAssignments = Get-TagAssignment -Entity $VM
#$AppName = $VMTagAssignments | Where-Object { $_.Tag.Category.Name -eq "AppName" }
#$DTAP = $VMTagAssignments | Where-Object { $_.Tag.Category.Name -eq "DTAP" }
Write-Verbose -Message "FQDN: $FQDN"
Write-Verbose -Message "HostName: $HostName"
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue
$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))
If ($null -eq $GuestVMLocalCredential) {
Write-Error -Message "Credentials not found in Passwordstate" -ErrorAction Stop
}
Else {
Write-Verbose -Message "Credentials found in Passwordstate"
Write-Verbose -Message ("username: + " + $GuestVMLocalCredential.UserName)
}
<# 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"
}
}
} #>
# 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 -Message "Join Active Directory"
Write-Verbose -Message "Domain is $DomainName"
Write-Verbose -Message "AppName is $AppName"
switch ($DomainName) {
'nd.gov' {
$SearchBaseDomain = "dc=nd,dc=gov"
}
'ndcloud.gov' {
$SearchBaseDomain = "dc=ndcloud,dc=gov"
}
}
### Shared-PeopleSoft-HigherEd is not found because of the extra space character in the OU name
### commented out section below is the original code, swapped out for new code 2025/01/13
# Determine if the AppName has a dedicated OU, and pick it... or choose the All-General OU
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 ($null -eq $OUAppName) {
Write-Verbose -Message "Dedicated AppName OU not found, placing in All-General OU"
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq "All-General" }
}
# Determine if the Environment sub-ou is special for the selected AppName
switch ($OUAppName.Name) {
'Shared-PeopleSoft-HigherEd' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
'Shared-PeopleSoft-State' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
Default {
switch ($VMEnvironment) {
'Test' { $EnvString = "Test" }
'Production' { $EnvString = "Prod" }
}
}
}
# Gather all sub OUs for the AppName OU
$SubOUs = Get-ADOrganizationalUnit -Server $DomainName -SearchBase $OUAppName -Filter *
If (@($SubOUs).count -eq 1) {
$OUToUse = $SubOUs
}
Else {
$OUToUse = $SubOUs | Where-Object { $_.DistinguishedName -like ("OU=$EnvString,OU=" + $OUAppName.Name + "*") }
}
# Determine if AD Computer object already exists
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $HostName }
If ($ExistingADComputer) {
Write-Verbose -Message "AD Object already exists in AD"
# Determine if AD Computer object is in the correct OU, or a sub-OU of the correct OU
If ($ExistingADComputer.DistinguishedName -like ("*" + $OUToUse.DistinguishedName)) {
Write-Verbose -Message "AD Object is in the correct OU, will attempt domain join"
$AttemptDomainJoin = $true
}
Else {
Write-Verbose -Message "AD Object is NOT in the correct OU, domain join will not occur, needs human review"
$AttemptDomainJoin = $false
}
}
Else {
$AttemptDomainJoin = $true
}
If ($AttemptDomainJoin) {
$OuFinal = $OUToUse.DistinguishedName
$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 itddc42.nd.gov'
$SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
$SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
Write-Verbose -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}");
}
<#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-Module -Name ITDSolarwinds -Verbose
Import-SWDiscovery -ComputerName $C -Integration ServiceNow
}
Invoke-Command -ComputerName itdslrwnds.nd.gov -ScriptBlock $Func -ArgumentList $FQDN -Credential $Credential
Write-Verbose -Message "[$FQDN]:End"
}
end {
}
}
@@ -0,0 +1,290 @@
<#
.SYNOPSIS
Decomissions a Windows Server device
.DESCRIPTION
Decomissions a Windows Server device with ITD specifications (DNS/Infoblox, Active Directory, SCCM)
.NOTES
Credential must be an nd.gov account with access to remove objects on all platforms involved: DNS/Infoblox, Active Directory, SCCM. Read access to vCenter in the future.
.EXAMPLE
Remove-ITDWindowsServer -ComputerName itdxxx.nd.gov -SCTaskNum SCTASKxxxxxxxxx -Credential $AdminCredential
#>
function Remove-ITDWindowsServer {
[CmdletBinding()]
param (
[string]
$ComputerName,
[string]
$SCTaskNum,
[Parameter(Mandatory = $true)]
[PSCredential]
$Credential
)
begin {
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
}
process {
$HostName = $ComputerName.split('.')[0]
# get current user, SCTask, Ritm, custom variables
switch ($env:username) {
'svcitdiaasauto' {
$assignTo = Get-ITDServiceNowUser -Username svcvmwareadm
}
Default {
$assignTo = Get-ITDServiceNowUser -Username $Env:username
}
}
$SCTask = Get-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum
$RitmNum = $SCTask.request_item.display_value
Write-Verbose -Message "Gathering $RitmNum"
$Ritm = Get-ITDServiceNowRecord -ItemType 'Request Item' -Number $RitmNum -IncludeVariableSet -ErrorAction Stop
###### the name in the $ComputerName parameter must match a name in the form to continue
Write-Verbose -Message "Gathering VariableSet data from $RitmNum"
$MatchFound = $false
ForEach ($Row in $Ritm.VariableSet) {
$TempCi = Get-ITDServiceNowRecord -Table cmdb_ci -SysId ($Row.host_name_ref) -ErrorAction Stop
If ($ComputerName -eq $TempCi.FQDN.display_value) {
$Ci = $TempCi
$MatchFound = $true
}
}
$FQDN = $Ci.fqdn.display_value
If ($MatchFound -eq $false) {
Write-Error -Message "ComputerName $ComputerName was not found in VariableSet for $RitmNum" -ErrorAction Stop
}
Write-Verbose -Message "Assigning SCTask to current user, maybe"
##### Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{assigned_to = $assignTo.name } REVIEW IF ASSIGNMENT SHOULD BE MADE HERE
$short_description = $SCTask.short_description.display_value
# Gather DNS FQDN and IP
$DNSResolve = Resolve-DnsName -Name $ComputerName -ErrorAction SilentlyContinue
If ($DNSResolve -eq $null) {
$DNSResolve = "DNS object not found"
}
$DNSInfo = $DNSResolve | ConvertTo-Json -WarningAction SilentlyContinue
# AD OU
$ADComputers = $null
$Domain = $ComputerName.Substring($ComputerName.IndexOf('.') + 1)
switch ($Domain) {
'nd.gov' {
try {
$ADComputers = Get-ADComputer -Identity $HostName -Properties * -ErrorAction SilentlyContinue
}
catch {
# empty because erroraction silentlycontinue doesn't work as expected
}
If ($ADComputers) {
$ADInfo = $ADComputers | ConvertTo-Json -WarningAction SilentlyContinue
}
Else {
$ADInfo = "AD object not found"
}
}
'Default' {
Write-Warning -Message "$ComputerName not nd.gov, review other domains manually"
}
}
# SCCM collections
$SCCMResult = Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
Set-Location ITD:\
$CMDevice = Get-CMDevice -Name $args[0]
If ($CMDevice) {
Get-ITDCMDeviceMemberOf -ComputerName $args[0]
}
Else {
"SCCM object not found"
}
}
$SCCMInfo = $SCCMResult | ConvertTo-Json -WarningAction SilentlyContinue
Write-Verbose -Message "Updating SCTask with discovered information"
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("DNS Information: `n " + $DNSInfo) }
If ($vCenterInfo) {
}
If ($AzureInfo) {
}
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("Active Directory Information: `n " + $ADInfo) }
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("SCCM Information: `n " + $SCCMInfo) }
$CommentsForWorkNotes = $null
try {
$Notes = '<div><strong><span style="color: #c00000;">Decommissioned ' + (Get-Date -UFormat "%Y/%m/%d %H:%M:%S") + '</span></strong></div>'
Write-Verbose -Message "All records with Title matching $ComputerName were marked as decommissioned in the Notes field."
Update-ITDPassword -Title $ComputerName -AppendNotes -Notes $Notes -Credential $Credential -Force
$CommentsForWorkNotes += ("`nPasswordstate: All records with Title matching $ComputerName were marked as decommissioned in the Notes field. ")
}
catch {
}
# Power off VM
switch ($Ci.model_id.display_value) {
{ $_ -like "*VMware*" } {
$hardware_platform = "VMware";
$hardware_type = 'Virtual Machine'
$VMs = Get-VM -Name $FQDN -ErrorAction SilentlyContinue | Where-Object { $_.ExtensionData.summary.config.ManagedBy.Type -ne "placeholderVm" }
switch ( @($VMs).count ) {
{ 0 } {
Write-Warning "$FQDN not found in vCenter... is it Azure? Or does it not exist?"
}
{ $_ -gt 1 } {
Write-Verbose -Message "Multiple virtual machines with name $FQDN were found."
Write-Error -Message ("Multiple virtual machines with name $FQDN were found. Are there SRM placeholders? If so, unconfigure SRM and run this again. If there are no placeholders, confirm the virtual machine name.") -ErrorAction Stop
}
1 {
Write-Verbose -Message 'One virtual machine with name $FQDN were found.'
$TagAssignment = Get-TagAssignment -Entity $VMs
$vCenterInfo = $TagAssignment | select Tag, Entity | ConvertTo-Json -Depth 1
If ($VMs.PowerState -eq 'PoweredOn') {
Write-Verbose -Message "Power off VMware VM $FQDN"
$CommentsForWorkNotes += ("`nVMware: VM $FQDN has been powered off. ")
$VMs | Stop-VM -Confirm:$false
}
If ($vCenterInfo) {
# enter work_notes into sctask
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{
work_notes = ("vCenter Information: `n " + $vCenterInfo)
}
}
}
}
}
{ $_ -like "*Microsoft Virtual Machine*" } {
$hardware_platform = "Azure";
$hardware_type = 'Virtual Machine'
}
{ $_ -like "*HP*" } {
$hardware_platform = 'HPE';
$hardware_type = 'Physical'
}
default { $hardware_platform = 'Other' }
}
# SCCM removal
If ($SCCMResult -eq "SCCM object not found") {
# do nothing
}
Else {
Write-Verbose -Message "Attempt SCCM removal"
try {
$Domain = $ComputerName.Substring($ComputerName.IndexOf('.') + 1)
switch ($Domain) {
'nd.gov' { $SCCMDomain = 'NDGOV' }
'itd.nd.gov' { $SCCMDomain = 'NDGOV' }
'k12.nd.us' { $SCCMDomain = 'K12' }
'stg.k12.nd.us' { $SCCMDomain = 'K12STG' }
'ndcloud.gov' { $SCCMDomain = 'NDCLOUD'}
}
Write-Verbose -Message ("$Computername is SCCM Domain $SCCMDomain")
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname, $SCCMDomain -ScriptBlock {
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
Set-Location ITD:\
$Devices = Get-CMDevice -Name $args[0] | Where-Object Domain -EQ $args[1]
$Devices | select Name, Domain
ForEach ($Device in $Devices) {
If ($Device.Domain -match $SCCMDomain) {
Write-Verbose -Message ("SCCM: Removing " + $Device.Name + " on " + $Device.Domain + " domain")
$Device | Remove-CMDevice -Confirm:$false -Force
}
}
}
$CommentsForWorkNotes += ("`nSCCM: Device named $Hostname was removed. ")
}
catch {
}
}
# Active Directory removal
If ($ADInfo -eq "AD object not found") {
# do nothing
}
Else {
try {
switch ($Domain) {
'nd.gov' {
$ADComputers = Get-ADComputer -Identity $HostName
switch ( @($ADComputers).count ) {
{ 0 } { "AD: Not found" }
{ $_ -gt 1 } { "AD: More than one found" }
{ 1 } {
Write-Verbose -Message "AD: One found, removing"
$ADComputers
$ADComputers | Remove-ADObject -Recursive -Credential $Credential -Confirm:$false
}
}
$CommentsForWorkNotes += ("`nActive Directory: AD computer object with name $Hostname was removed from the nd.gov domain. ")
}
'Default' {
Write-Warning -Message "$ComputerName not nd.gov"
}
}
}
catch {
}
}
# DNS removal
If ($DNSResolve -eq "DNS object not found") {
# do nothing
}
Else {
try {
Write-Verbose "Attempting DNS removal"
Remove-ITDIbDnsRecord -ComputerName $ComputerName -Credential $RadiusCred
$CommentsForWorkNotes += ("`nInfoblox: DNS A Record was removed for $ComputerName. ")
}
catch {
}
}
# Solarwinds removal
try {
Remove-ITDSolarwindsNode -ComputerName $ComputerName -Credential $Credential
$CommentsForWorkNotes += ("`nSolarwinds: Node was removed for $ComputerName. ")
}
catch {
}
$CommentsForWorkNotes += ("`n `nWindows OS Decommission completed. $hardware_platform $hardware_type hardware is ready for removal. ")
$HardwareRemovalDescription = ("$ComputerName $hardware_platform $hardware_type hardware is ready for removal.")
Write-Verbose -Message "Add final comments for work notes into the SCTask, update the short_description for next workflow step"
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{
work_notes = $CommentsForWorkNotes;
short_description = $HardwareRemovalDescription;
}
}
end {
}
}
@@ -0,0 +1,19 @@
# figure out where to put it
$x = Get-ADComputer -Filter * -Server itdk12dc7.k12.nd.us -Credential $K12Cred -Properties CanonicalName
# enter this on the machine itself
$Credential = Get-Credential
$AddComputerParams = @{
DomainName = 'k12.nd.us';
OUPath = 'OU=Prod,OU=All-General,OU=Computers,OU=ITD,DC=k12,DC=nd,DC=us' ;
Credential = $Credential;
}
Add-Computer @AddComputerParams
@@ -0,0 +1,29 @@
$Step1Complete = $null
$SCTaskNum = ''
try {
$FQDN = 'itdcndhh22ywt.nd.gov'
$CIDR = '10.221.10.96/28'
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Write-Verbose -Message "New-ITDWindowsVmVMwareStep1 function completed"
$Step1Complete = $true
}
catch {
#Write-Error -Message $error[0]
$Step1Complete = $false
$ErrorText = ($_.ErrorDetails.message | ConvertFrom-Json).text
If ($ErrorText -match "Cannot find 1 available IP address" ) {
$Msg = "Resolve the issue and resubmit the Server Build Request catalog item. Setting $SCTaskNum State to Closed Incomplete"
Write-Warning -Message $Msg
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{ #>
work_notes = ("VMware build step 1 errored. $Msg. `nPSU Job Id #" + '52928' + " `n" + $ErrorText)
state = 'Closed Incomplete'
close_notes = ("VMware build step 1 errored. $Msg. `nPSU Job Id #" + '52928' + " `n" + $ErrorText)
}
}
throw
}