Public/Move-ServiceDHCP.ps1
function Move-ServiceDHCP { <# .SYNOPSIS Migrates DHCP service configuration from one host to another with comprehensive backup. .DESCRIPTION This function performs a complete DHCP service migration including: - Creates backup of DHCP scopes, reservations, and server options - Exports DHCP configuration from source server - Imports DHCP configuration to destination server - Migrates server options, classes, and policies - Validates migration success - Comprehensive logging of all operations .PARAMETER SourceServer The source DHCP server hostname or IP address to migrate from. .PARAMETER DestinationServer The destination DHCP server hostname or IP address to migrate to. .PARAMETER BackupPath Path where backups will be stored. Defaults to C:\Backups\DHCP_Migration. .PARAMETER ScopeIds Optional array of specific scope IDs to migrate. If not specified, all scopes will be migrated. .PARAMETER SkipValidation Skip post-migration validation checks. .PARAMETER Credential Credential object for accessing remote servers. .PARAMETER TransferLeases Transfer active DHCP leases from source to destination server. .EXAMPLE Move-ServiceDHCP -SourceServer "dhcp01.contoso.com" -DestinationServer "dhcp02.contoso.com" Migrates all DHCP scopes and settings from dhcp01 to dhcp02. .EXAMPLE Move-ServiceDHCP -SourceServer "192.168.1.10" -DestinationServer "192.168.1.20" -ScopeIds @("192.168.1.0", "10.0.1.0") -WhatIf Shows what would be migrated for specific scopes without executing. .NOTES Author: Forthencho Module Requires: DHCP Server PowerShell module, Administrative privileges Version: 1.0 #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SourceServer, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DestinationServer, [Parameter()] [ValidateScript({ $parentPath = Split-Path $_ -Parent if (-not (Test-Path $parentPath)) { try { New-Item -Path $parentPath -ItemType Directory -Force -ErrorAction Stop | Out-Null return $true } catch { throw "Cannot create backup directory: $parentPath" } } return $true })] [string]$BackupPath = "C:\Backups\DHCP_Migration", [Parameter()] [string[]]$ScopeIds = @(), [Parameter()] [switch]$SkipValidation, [Parameter()] [switch]$TransferLeases, [Parameter()] [System.Management.Automation.PSCredential]$Credential ) begin { # Initialize logging and backup directories $timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $migrationBackupPath = Join-Path $BackupPath $timestamp if (-not (Test-Path $migrationBackupPath)) { try { New-Item -Path $migrationBackupPath -ItemType Directory -Force | Out-Null Write-Verbose "Created backup directory: $migrationBackupPath" } catch { throw "Failed to create backup directory '$migrationBackupPath': $($_.Exception.Message)" } } $logFile = Join-Path $migrationBackupPath "DHCP_Migration_$timestamp.log" # Helper function for logging function Write-MigrationLog { param( [Parameter(Mandatory)] [string]$Message, [Parameter()] [ValidateSet('INFO', 'WARNING', 'ERROR', 'SUCCESS')] [string]$Level = 'INFO' ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "[$timestamp] [$Level] $Message" # Write to console with colors switch ($Level) { 'INFO' { Write-Host $logEntry -ForegroundColor White } 'WARNING' { Write-Warning $logEntry } 'ERROR' { Write-Host $logEntry -ForegroundColor Red } 'SUCCESS' { Write-Host $logEntry -ForegroundColor Green } } # Write to log file try { Add-Content -Path $logFile -Value $logEntry -ErrorAction Stop } catch { Write-Warning "Failed to write to log file: $($_.Exception.Message)" } } # Validate DHCP Server module availability if (-not (Get-Module -ListAvailable -Name DhcpServer)) { throw "DHCP Server PowerShell module is not available. Please install DHCP Server role and management tools." } Import-Module DhcpServer -ErrorAction Stop Write-MigrationLog "Starting DHCP migration from $SourceServer to $DestinationServer" -Level 'INFO' Write-MigrationLog "Backup location: $migrationBackupPath" -Level 'INFO' Write-MigrationLog "WhatIf mode: $($WhatIfPreference.IsPresent)" -Level 'INFO' } process { try { # Step 1: Backup DHCP configuration from source server Write-MigrationLog "=== Step 1: Creating backup of source DHCP configuration ===" -Level 'INFO' $sourceBackupPath = Join-Path $migrationBackupPath "Source_$SourceServer" New-Item -Path $sourceBackupPath -ItemType Directory -Force | Out-Null # Get DHCP server information Write-MigrationLog "Retrieving DHCP configuration from source server: $SourceServer" -Level 'INFO' $sessionParams = @{ ComputerName = $SourceServer ErrorAction = 'Stop' } if ($Credential) { $sessionParams.Credential = $Credential } try { # Get DHCP server settings and scopes $dhcpInfo = Invoke-Command @sessionParams -ScriptBlock { Import-Module DhcpServer -Force $serverInfo = @{ ServerSettings = Get-DhcpServerSetting Database = Get-DhcpServerDatabase Scopes = Get-DhcpServerv4Scope ServerOptions = Get-DhcpServerv4OptionValue -All -ErrorAction SilentlyContinue Classes = Get-DhcpServerv4Class -ErrorAction SilentlyContinue Policies = Get-DhcpServerv4Policy -ErrorAction SilentlyContinue Bindings = Get-DhcpServerv4Binding -ErrorAction SilentlyContinue } return $serverInfo } Write-MigrationLog "Found $($dhcpInfo.Scopes.Count) DHCP scopes on source server" -Level 'SUCCESS' # Filter scopes if specific scope IDs were requested if ($ScopeIds.Count -gt 0) { $dhcpInfo.Scopes = $dhcpInfo.Scopes | Where-Object { $_.ScopeId.IPAddressToString -in $ScopeIds } Write-MigrationLog "Filtered to $($dhcpInfo.Scopes.Count) specified scopes" -Level 'INFO' } # Export DHCP server information $serverBackupFile = Join-Path $sourceBackupPath "DHCP_Server_Info.json" $dhcpInfo | ConvertTo-Json -Depth 10 | Out-File -FilePath $serverBackupFile -Encoding UTF8 Write-MigrationLog "Backed up DHCP server information to: $serverBackupFile" -Level 'SUCCESS' } catch { Write-MigrationLog "Failed to retrieve DHCP information from source server: $($_.Exception.Message)" -Level 'ERROR' throw } # Step 2: Export detailed scope configurations Write-MigrationLog "=== Step 2: Exporting detailed scope configurations ===" -Level 'INFO' foreach ($scope in $dhcpInfo.Scopes) { try { Write-MigrationLog "Exporting scope: $($scope.ScopeId)" -Level 'INFO' if ($WhatIfPreference) { Write-MigrationLog "WHATIF: Would export scope $($scope.ScopeId)" -Level 'INFO' } else { # Get detailed scope information $scopeDetails = Invoke-Command @sessionParams -ScriptBlock { param($scopeId) $scopeInfo = @{ Scope = Get-DhcpServerv4Scope -ScopeId $scopeId Options = Get-DhcpServerv4OptionValue -ScopeId $scopeId -All -ErrorAction SilentlyContinue Reservations = Get-DhcpServerv4Reservation -ScopeId $scopeId -ErrorAction SilentlyContinue ExclusionRanges = Get-DhcpServerv4ExclusionRange -ScopeId $scopeId -ErrorAction SilentlyContinue Policies = Get-DhcpServerv4Policy -ScopeId $scopeId -ErrorAction SilentlyContinue Statistics = Get-DhcpServerv4ScopeStatistics -ScopeId $scopeId -ErrorAction SilentlyContinue } # Get leases if requested if ($using:TransferLeases) { $scopeInfo.Leases = Get-DhcpServerv4Lease -ScopeId $scopeId -ErrorAction SilentlyContinue } return $scopeInfo } -ArgumentList $scope.ScopeId.IPAddressToString # Save scope details $scopeBackupFile = Join-Path $sourceBackupPath "Scope_$($scope.ScopeId.IPAddressToString -replace '\.', '_').json" $scopeDetails | ConvertTo-Json -Depth 10 | Out-File -FilePath $scopeBackupFile -Encoding UTF8 Write-MigrationLog "Exported scope $($scope.ScopeId) with $($scopeDetails.Reservations.Count) reservations" -Level 'SUCCESS' if ($TransferLeases -and $scopeDetails.Leases) { Write-MigrationLog "Backed up $($scopeDetails.Leases.Count) active leases for scope $($scope.ScopeId)" -Level 'INFO' } } } catch { Write-MigrationLog "Failed to export scope $($scope.ScopeId): $($_.Exception.Message)" -Level 'ERROR' } } # Step 3: Export DHCP database backup (if possible) Write-MigrationLog "=== Step 3: Creating DHCP database backup ===" -Level 'INFO' try { if ($WhatIfPreference) { Write-MigrationLog "WHATIF: Would create DHCP database backup" -Level 'INFO' } else { $dbBackupResult = Invoke-Command @sessionParams -ScriptBlock { $backupPath = "$env:TEMP\DHCPBackup_$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss')" try { Backup-DhcpServer -Path $backupPath return $backupPath } catch { Write-Warning "Could not create database backup: $($_.Exception.Message)" return $null } } if ($dbBackupResult) { # Copy database backup to local backup directory $localDbBackupPath = Join-Path $sourceBackupPath "Database_Backup" $remoteDbBackupPath = "\\$SourceServer\C$\$($dbBackupResult.Replace('C:\', '').Replace(':', '$'))" try { Copy-Item -Path $remoteDbBackupPath -Destination $localDbBackupPath -Recurse -Force -ErrorAction Stop Write-MigrationLog "Copied DHCP database backup to local storage" -Level 'SUCCESS' } catch { Write-MigrationLog "Could not copy database backup: $($_.Exception.Message)" -Level 'WARNING' } } } } catch { Write-MigrationLog "Database backup failed: $($_.Exception.Message)" -Level 'WARNING' } # Step 4: Prepare destination server Write-MigrationLog "=== Step 4: Preparing destination server ===" -Level 'INFO' $destSessionParams = @{ ComputerName = $DestinationServer ErrorAction = 'Stop' } if ($Credential) { $destSessionParams.Credential = $Credential } try { # Verify destination server is accessible and has DHCP role $destDhcpInfo = Invoke-Command @destSessionParams -ScriptBlock { Import-Module DhcpServer -Force Get-DhcpServerSetting } Write-MigrationLog "Destination server $DestinationServer is accessible and has DHCP role installed" -Level 'SUCCESS' # Create backup of destination server (before migration) if (-not $WhatIfPreference) { $destBackupPath = Join-Path $migrationBackupPath "Destination_$DestinationServer" New-Item -Path $destBackupPath -ItemType Directory -Force | Out-Null $destScopes = Invoke-Command @destSessionParams -ScriptBlock { Get-DhcpServerv4Scope -ErrorAction SilentlyContinue } if ($destScopes) { $destScopes | ConvertTo-Json -Depth 10 | Out-File -FilePath (Join-Path $destBackupPath "Pre_Migration_Scopes.json") -Encoding UTF8 Write-MigrationLog "Created pre-migration backup of destination server ($($destScopes.Count) existing scopes)" -Level 'SUCCESS' } } } catch { Write-MigrationLog "Failed to prepare destination server: $($_.Exception.Message)" -Level 'ERROR' throw } # Step 5: Import DHCP configuration to destination server Write-MigrationLog "=== Step 5: Importing DHCP configuration to destination server ===" -Level 'INFO' foreach ($scope in $dhcpInfo.Scopes) { try { Write-MigrationLog "Importing scope: $($scope.ScopeId) to destination server" -Level 'INFO' if ($WhatIfPreference) { Write-MigrationLog "WHATIF: Would import scope $($scope.ScopeId) to $DestinationServer" -Level 'INFO' continue } # Load detailed scope configuration $scopeBackupFile = Join-Path $sourceBackupPath "Scope_$($scope.ScopeId.IPAddressToString -replace '\.', '_').json" if (-not (Test-Path $scopeBackupFile)) { Write-MigrationLog "Scope backup file not found: $scopeBackupFile" -Level 'WARNING' continue } $scopeDetails = Get-Content $scopeBackupFile | ConvertFrom-Json # Check if scope already exists on destination $existingScope = Invoke-Command @destSessionParams -ScriptBlock { param($scopeId) Get-DhcpServerv4Scope -ScopeId $scopeId -ErrorAction SilentlyContinue } -ArgumentList $scope.ScopeId.IPAddressToString if ($existingScope) { Write-MigrationLog "Scope $($scope.ScopeId) already exists on destination. Creating backup..." -Level 'WARNING' # Backup existing scope $existingScopeBackup = Join-Path $destBackupPath "Existing_Scope_$($scope.ScopeId.IPAddressToString -replace '\.', '_').json" $existingScope | ConvertTo-Json -Depth 10 | Out-File -FilePath $existingScopeBackup -Encoding UTF8 # Remove existing scope Invoke-Command @destSessionParams -ScriptBlock { param($scopeId) Remove-DhcpServerv4Scope -ScopeId $scopeId -Force } -ArgumentList $scope.ScopeId.IPAddressToString Write-MigrationLog "Removed existing scope $($scope.ScopeId)" -Level 'INFO' } # Create the scope Invoke-Command @destSessionParams -ScriptBlock { param($scopeData) $scopeParams = @{ ScopeId = $scopeData.Scope.ScopeId Name = $scopeData.Scope.Name StartRange = $scopeData.Scope.StartRange EndRange = $scopeData.Scope.EndRange SubnetMask = $scopeData.Scope.SubnetMask Description = $scopeData.Scope.Description State = $scopeData.Scope.State } Add-DhcpServerv4Scope @scopeParams } -ArgumentList $scopeDetails Write-MigrationLog "Created scope $($scope.ScopeId)" -Level 'SUCCESS' # Import exclusion ranges if ($scopeDetails.ExclusionRanges) { foreach ($exclusion in $scopeDetails.ExclusionRanges) { try { Invoke-Command @destSessionParams -ScriptBlock { param($scopeId, $startRange, $endRange) Add-DhcpServerv4ExclusionRange -ScopeId $scopeId -StartRange $startRange -EndRange $endRange } -ArgumentList $scope.ScopeId.IPAddressToString, $exclusion.StartRange, $exclusion.EndRange } catch { Write-MigrationLog "Failed to import exclusion range for scope $($scope.ScopeId): $($_.Exception.Message)" -Level 'WARNING' } } Write-MigrationLog "Imported $($scopeDetails.ExclusionRanges.Count) exclusion ranges for scope $($scope.ScopeId)" -Level 'SUCCESS' } # Import scope options if ($scopeDetails.Options) { foreach ($option in $scopeDetails.Options) { try { Invoke-Command @destSessionParams -ScriptBlock { param($scopeId, $optionId, $value, $vendorClass, $userClass) $setParams = @{ ScopeId = $scopeId OptionId = $optionId Value = $value } if ($vendorClass) { $setParams.VendorClass = $vendorClass } if ($userClass) { $setParams.UserClass = $userClass } Set-DhcpServerv4OptionValue @setParams } -ArgumentList $scope.ScopeId.IPAddressToString, $option.OptionId, $option.Value, $option.VendorClass, $option.UserClass } catch { Write-MigrationLog "Failed to import option $($option.OptionId) for scope $($scope.ScopeId): $($_.Exception.Message)" -Level 'WARNING' } } Write-MigrationLog "Imported $($scopeDetails.Options.Count) scope options for scope $($scope.ScopeId)" -Level 'SUCCESS' } # Import reservations if ($scopeDetails.Reservations) { foreach ($reservation in $scopeDetails.Reservations) { try { Invoke-Command @destSessionParams -ScriptBlock { param($scopeId, $ipAddress, $clientId, $name, $description, $type) Add-DhcpServerv4Reservation -ScopeId $scopeId -IPAddress $ipAddress -ClientId $clientId -Name $name -Description $description -Type $type } -ArgumentList $scope.ScopeId.IPAddressToString, $reservation.IPAddress, $reservation.ClientId, $reservation.Name, $reservation.Description, $reservation.Type } catch { Write-MigrationLog "Failed to import reservation $($reservation.IPAddress) for scope $($scope.ScopeId): $($_.Exception.Message)" -Level 'WARNING' } } Write-MigrationLog "Imported $($scopeDetails.Reservations.Count) reservations for scope $($scope.ScopeId)" -Level 'SUCCESS' } # Import leases if requested if ($TransferLeases -and $scopeDetails.Leases) { foreach ($lease in $scopeDetails.Leases) { try { Invoke-Command @destSessionParams -ScriptBlock { param($scopeId, $ipAddress, $clientId, $leaseExpiryTime) Add-DhcpServerv4Lease -ScopeId $scopeId -IPAddress $ipAddress -ClientId $clientId -LeaseExpiryTime $leaseExpiryTime } -ArgumentList $scope.ScopeId.IPAddressToString, $lease.IPAddress, $lease.ClientId, $lease.LeaseExpiryTime } catch { Write-MigrationLog "Failed to import lease $($lease.IPAddress) for scope $($scope.ScopeId): $($_.Exception.Message)" -Level 'WARNING' } } Write-MigrationLog "Imported $($scopeDetails.Leases.Count) active leases for scope $($scope.ScopeId)" -Level 'SUCCESS' } } catch { Write-MigrationLog "Failed to import scope $($scope.ScopeId): $($_.Exception.Message)" -Level 'ERROR' } } # Step 6: Apply server-level settings Write-MigrationLog "=== Step 6: Applying server-level DHCP settings ===" -Level 'INFO' if (-not $WhatIfPreference) { try { # Apply server options if ($dhcpInfo.ServerOptions) { foreach ($serverOption in $dhcpInfo.ServerOptions) { try { Invoke-Command @destSessionParams -ScriptBlock { param($optionId, $value, $vendorClass, $userClass) $setParams = @{ OptionId = $optionId Value = $value } if ($vendorClass) { $setParams.VendorClass = $vendorClass } if ($userClass) { $setParams.UserClass = $userClass } Set-DhcpServerv4OptionValue @setParams } -ArgumentList $serverOption.OptionId, $serverOption.Value, $serverOption.VendorClass, $serverOption.UserClass } catch { Write-MigrationLog "Failed to apply server option $($serverOption.OptionId): $($_.Exception.Message)" -Level 'WARNING' } } Write-MigrationLog "Applied $($dhcpInfo.ServerOptions.Count) server options" -Level 'SUCCESS' } # Apply server settings (conflict detection, DNS settings, etc.) if ($dhcpInfo.ServerSettings) { Invoke-Command @destSessionParams -ScriptBlock { param($settings) try { Set-DhcpServerSetting -ConflictDetectionAttempts $settings.ConflictDetectionAttempts -DynamicBootp $settings.DynamicBootp -IsAuthorized $settings.IsAuthorized } catch { Write-Warning "Some server settings could not be applied: $($_.Exception.Message)" } } -ArgumentList $dhcpInfo.ServerSettings Write-MigrationLog "Applied server settings" -Level 'SUCCESS' } } catch { Write-MigrationLog "Failed to apply some server settings: $($_.Exception.Message)" -Level 'WARNING' } } else { Write-MigrationLog "WHATIF: Would apply server-level DHCP settings to destination" -Level 'INFO' } # Step 7: Validation if (-not $SkipValidation -and -not $WhatIfPreference) { Write-MigrationLog "=== Step 7: Validating migration ===" -Level 'INFO' try { $destScopesAfter = Invoke-Command @destSessionParams -ScriptBlock { Get-DhcpServerv4Scope } $validationResults = @{ TotalSourceScopes = $dhcpInfo.Scopes.Count TotalDestinationScopes = $destScopesAfter.Count MigratedScopes = @() MissingScopes = @() ValidationPassed = $true } foreach ($sourceScope in $dhcpInfo.Scopes) { $destScope = $destScopesAfter | Where-Object { $_.ScopeId.IPAddressToString -eq $sourceScope.ScopeId.IPAddressToString } if ($destScope) { $validationResults.MigratedScopes += $sourceScope.ScopeId.IPAddressToString } else { $validationResults.MissingScopes += $sourceScope.ScopeId.IPAddressToString $validationResults.ValidationPassed = $false } } # Save validation results $validationFile = Join-Path $migrationBackupPath "Migration_Validation.json" $validationResults | ConvertTo-Json -Depth 10 | Out-File -FilePath $validationFile -Encoding UTF8 if ($validationResults.ValidationPassed) { Write-MigrationLog "Migration validation PASSED - All scopes migrated successfully" -Level 'SUCCESS' } else { Write-MigrationLog "Migration validation FAILED - Missing scopes: $($validationResults.MissingScopes -join ', ')" -Level 'ERROR' } } catch { Write-MigrationLog "Validation failed: $($_.Exception.Message)" -Level 'WARNING' } } else { Write-MigrationLog "Skipping validation (SkipValidation=$SkipValidation, WhatIf=$($WhatIfPreference.IsPresent))" -Level 'INFO' } Write-MigrationLog "DHCP migration completed" -Level 'SUCCESS' } catch { Write-MigrationLog "DHCP migration failed: $($_.Exception.Message)" -Level 'ERROR' throw } } end { Write-MigrationLog "=== Migration Summary ===" -Level 'INFO' Write-MigrationLog "Source Server: $SourceServer" -Level 'INFO' Write-MigrationLog "Destination Server: $DestinationServer" -Level 'INFO' Write-MigrationLog "Backup Location: $migrationBackupPath" -Level 'INFO' Write-MigrationLog "Log File: $logFile" -Level 'INFO' Write-MigrationLog "Transfer Leases: $TransferLeases" -Level 'INFO' if ($WhatIfPreference) { Write-Host "`nWhatIf mode was enabled - no actual changes were made." -ForegroundColor Cyan } Write-Host "`nMigration backup and logs saved to: $migrationBackupPath" -ForegroundColor Cyan # Return migration results return @{ BackupPath = $migrationBackupPath LogFile = $logFile SourceServer = $SourceServer DestinationServer = $DestinationServer WhatIfMode = $WhatIfPreference.IsPresent TransferLeases = $TransferLeases.IsPresent } } } |