Suite de notre série d’articles sur Powershell DSC, nous allons maintenant capitaliser sur l’infrastructure que nous avons mise en place (voir articles précédents) pour configurer un nouveau contrôleur de domaine Active Directory (DC).
Il nous faut récupérer le module xActiveDirectory.
Sur le serveur pull, nous allons dézipper le module et le placer dans C:\Program Files\WindowsPowerShell\Modules.
Nous allons également placer le zip ici: C:\Program Files\WindowsPowerShell\DscService\Modules.
Enfin nous allons régénérer les checksums des modules à distribuer aux nouvelles machines, et pour cela nous allons exécuter le code suivant:
1 2 |
$DscServiceModules = "$env:ProgramFiles\WindowsPowershell\DscService\Modules" New-DscCheckSum -ConfigurationPath $DscServiceModules -OutPath $DscServiceModules -Verbose -Force |
Voici le résultat:
(le module xNetworking était déjà présent et est le résultat d’un des articles précédents)
Nous allons maintenant modifier notre script de génération et distribution de configurations en 4 parties:
Dans la fonction de configuration (ServerConfig), nous allons importer le module xActiveDirectory:
1 |
Import-DSCResource -ModuleName xActiveDirectory |
Ensuite dans la partie Node $AllNodes.NodeName, nous allons faire deux choses:
Ceci installe la feature AD-Domain-Services, c’est comme si on ajoutait le rôle avec Server Manager.
1 2 3 4 5 |
WindowsFeature ADDSInstall { Ensure = "Present" Name = "AD-Domain-Services" } |
Nous allons utiliser la ressource xADDomainController qui permet de promouvoir un serveur en contrôleur de domaine. C’est comme si on faisait un DCPROMO dans les anciennes versions de Windows Server.
1 2 3 4 5 6 7 8 9 10 |
xADDomainController DC { SafemodeAdministratorPassword = $safemodePass DomainAdministratorCredential = $credential DomainName = $Node.DomainName DatabasePath = "C:\NTDS" LogPath = "C:\LOG" SysvolPath = "C:\SYSVOL" DependsOn = "[WindowsFeature]ADDSInstall" } |
On peut voir dans la ressource les éléments suivants:
- SafemodeAdministratorPassword, il s’agît du mot de passe utilisé lorsque on veut entrer dans le mode de restauration (safe mode) du contrôleur de domaine.
- DomainAdministratorCredential, ce sont les credentials d’un compte admin du domaine.
- DomainName, le nom du domaine. A remarquer j’ai basculé cette info dans le $ConfigData du script, on sépare le quoi, du où.
- DatabasePath, LogPath, SysvolPath, à customiser si besoin.
- DependsOn, Nous voulons nous assurer que si le rôle AD DS est installé avant faire la promotion du DC.
Finalement, nous allons modifier notre $configData ainsi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ConfigData = @{ AllNodes = @( @{ NodeName = "*" CertificateFile = "$strPath" Thumbprint = $thumbprint }, @{ NodeName=$GUID DomainName = "ad.local" CertificateFile = "$strPath" Thumbprint = $thumbprint } ) } |
On voit que c’est ici qu’on renseigne le nom du domaine. Ainsi je peux planifier de modifier le script pour ajouter plusieurs domaines, si besoin.
Voici le script entier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
<# .SYNOPSIS Publish-ServerConfigurations - DC version Generates and deploys MOF configuration files to given servers .DESCRIPTION This script generates MOF files and uses Powershell DSC to distribute configurations to given servers This script contains two cmdlets: New-ServerConfigurations Export-ServerConfigurations This script is generally executed from a DSC enabled PULL server. Configuration files will be copied to $env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration .PARAMETER Servers Array list of target servers that will receive the new configurations .PARAMETER Thumbprint Thumbprint of the Certificate that will be used to decrypt the MOF files, the certificate of this thumbprint must be stored in cert:\LocalMachine\My This script will deploy and import the certificate. .PARAMETER Path Working Path where all the temporary files will be created prior to deploying them to their final location. Generally C:\DSC. .PARAMETER PublicKey Name of the Public Key file. Generally a .cer file. This file is expected to be stored in the $Path folder. .PARAMETER PrivateKey Name of the Private Key file. Generally a .pfx file. This file is expected to be stored in the $Path folder. To generate the proper certificate, make a copy of the Computer template, and ensure that the Private Key can be exported. .PARAMETER PassFile A file containing the encrypted password to be used to import the certificate (pfx file defined in PrivateKey) on the target server. You can use the following code to generate the password file: Function New-PasswordTextFile{ param([string] $filename) read-host -assecurestring | convertfrom-securestring | out-file $filename } New-PasswordTextFile -filename "c:\dsc\certpass.txt" .PARAMETER Credential Credential used in the MOF configuration file. These credentials will be encrypted in the MOF file and decrypted by the target server. .NOTES Filename : Publish-ServerConfigurations - DC.ps1 Author(s) : Micky Balladelli Mehdi Jerbi .LINK https://balladelli.com/powershell-dsc-add-dc/ #> Param( [String[]]$Servers = @("DC2"), [String]$Thumbprint = "412952E8227BB605417FEB072F4C2F517817B010", [String]$ServerURL = "https://pull.ad.local:8080/PSDSCPullServer.svc", [String]$Path = "C:\DSC", [String]$PublicKey = "PULLPublicKey.cer", [String]$Privatekey = "PULLprivatekey.pfx", [String]$PassFile = "c:\dsc\certpass.txt", [PsCredential]$Credential) $certPass = get-content $passFile | convertto-securestring if ($Credential -eq $null) { $credential = Get-Credential -Message "Please enter credentials required in the configuration" -username "$($ENV:userdomain)\$($ENV:username)" } $safemodePass = Get-Credential -Message "Please enter password to set safe mode" -username "none\ignored" Configuration ServerConfig { Param( [String]$GUID, [PsCredential]$Credential, [PsCredential]$safemodePass) Import-DSCResource -ModuleName xNetworking Import-DSCResource -ModuleName xActiveDirectory Node $AllNodes.NodeName { LocalConfigurationManager { CertificateId = $node.Thumbprint } Script GetOSVersion { TestScript = { $version = (Get-CimInstance Win32_OperatingSystem).version return ([Version] 6.3 -le $version) } GetScript = { return @{} } SetScript = {$a = 1} } Registry DNSsearchListADINT { Ensure = "Present" Key = "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\TCPIP\Parameters" ValueName = "SearchList" ValueData = "ad.local,balladelli.com" DependsOn = "[Script]GetOSVersion" } WindowsFeature ADDSInstall { Ensure = "Present" Name = "AD-Domain-Services" DependsOn = "[Script]GetOSVersion" } xADDomainController DC { SafemodeAdministratorPassword = $safemodePass DomainAdministratorCredential = $credential DomainName = $Node.DomainName DatabasePath = "C:\NTDS" LogPath = "C:\LOG" SysvolPath = "C:\SYSVOL" DependsOn = "[WindowsFeature]ADDSInstall" } Log Done { # The message below gets written to the Microsoft-Windows-Desired State Configuration/Analytic log Message = "DSC promotion of the server as a domain controller - is DONE" DependsOn = "[xADDomainController]DC" } } } function New-ServerConfigurations { [CmdletBinding()] Param( $Path = "C:\DSC", [String[]]$Servers, [switch]$Force, [PSCredential]$Credential, $Thumbprint, $Publickey) begin { $configurationsPath = "$path\Configurations" $configGenerated = $false $targetFiles = "$env:SystemDrive\Program Files\WindowsPowershell\DscService\Configuration" if ($Force.IsPresent) { Write-Verbose "Cleaning up old config files" Remove-Item "$targetFiles\*.mof*" -ErrorAction SilentlyContinue if (test-path "$configurationsPath\servers.csv") { remove-item "$configurationsPath\servers.csv" } $dataArray = @() } else { if (test-path "$configurationsPath\servers.csv") { $dataArray = import-csv "$configurationsPath\servers.csv" -header("Server","GUID") } } } process { foreach ($server in $Servers) { Write-host "Creating MOF files for $server" $found = $false # Only check if the configuration for the given server has been created if the Force param is not present. # If Force is present, always generate new configurations if($Force.IsPresent -eq $false) { foreach ($elem in $dataArray) { if ($server -eq $elem.server) { Write-Verbose "Server $server already has a configuration file, use -Force to generate a new file" $found = $true break } } } if ($found -eq $false) { $GUID = [guid]::NewGuid().ToString() $strPath = "$Path\$publickey" $ConfigData = @{ AllNodes = @( @{ NodeName = "*" CertificateFile = "$strPath" Thumbprint = $thumbprint }, @{ NodeName=$GUID DomainName = "ad.local" CertificateFile = "$strPath" Thumbprint = $thumbprint } ) } $serverData = New-Object PSCustomObject -Property @{ GUID = $GUID; Server = $server } $newLine = "{0},{1}" -f $server,$GUID $result = ServerConfig -GUID $Guid -Output "$configurationsPath\ServerConfig" -Credential $credential -safemodePass $safemodePass -ConfigurationData $ConfigData $newLine | add-content -path "$configurationsPath\Servers.csv" $serverConfig = "$configurationsPath\ServerConfig" $configGenerated = $true $result = New-DSCCheckSum -ConfigurationPath $serverConfig -OutPath $serverConfig -Force -Verbose $dataArray += $serverData } } } end { if ($configGenerated) { Write-Verbose "Copying configurations to DSC service configuration store..." $sourceFiles = "$configurationsPath\ServerConfig\*.mof*" Move-Item $sourceFiles $targetFiles -Force -Verbose Remove-Item $sourceFiles return [array]$dataArray } } } function Export-ServerConfigurations { [CmdletBinding()] Param( $path = "C:\DSC", [String[]]$servers, $CertPass, $thumbprint, $privatekey, [array]$configurations, $serverURL) begin { } process { # Distribute the new configurations foreach ($server in $servers) { write-host "Deploying configuration to $server" # Ensure the certificate is installed on the remote server [array] $Thumbprints = Invoke-Command -ComputerName $server -ArgumentList @($thumbprint) -ScriptBlock { $thumbprint = $args[0] (dir Cert:\LocalMachine\My) | %{ # Verify the certificate is for Encryption and valid if ($_.thumbprint -eq $thumbprint) { return $_.Thumbprint } } } $found = $Thumbprints | ? { $_ -eq $thumbprint} if ($found -eq $null) { write-host "Certificate (thumbprint: $thumbprint) required to decrypt the configuration file is missing on $server, copying file" copy-Item "$path\$privatekey" "\\$server\c$\temp\$privatekey" Invoke-Command -ComputerName $server -ArgumentList @($privatekey,$certpass) -ScriptBlock { $cert = $args[0] $pass = $args[1] Import-PfxCertificate –FilePath "c:\temp\$cert" cert:\localMachine\my -Password $pass } Remove-Item "\\$server\c$\temp\$privatekey" } $GUID = ($configurations | where-object {$_."Server" -eq $server}).GUID Invoke-Command -ComputerName $server -ArgumentList @($GUID,$thumbprint,$serverURL) -ScriptBlock { Configuration DiscoverConfigurationForPull { Param([String]$GUID, [String]$thumbprint, [String]$serverURL) $serverURLhashtable = @{ServerUrl = "$serverURL"} LocalConfigurationManager { ConfigurationID = $GUID; CertificateId = $thumbprint; RefreshMode = "PULL"; DownloadManagerName = "WebDownloadManager"; RebootNodeIfNeeded = $true; RefreshFrequencyMins = 30; ConfigurationModeFrequencyMins = 30; ConfigurationMode = "ApplyAndAutoCorrect"; DownloadManagerCustomData = @{ServerUrl = "$serverURL"} } } $GUID = $args[0] $thumbprint = $args[1] $serverURL = $args[2] $result = DiscoverConfigurationForPull -GUID $GUID -thumbprint $thumbprint -serverURL $serverURL -Output "." $FilePath = (Get-Location -PSProvider FileSystem).Path + "\DiscoverConfigurationForPull" Set-DscLocalConfigurationManager -ComputerName "localhost" -Path $FilePath -Verbose } } } end { } } [array] $configurations = New-ServerConfigurations -path $path -Servers $Servers -Force -Credential $credential -thumbprint $thumbprint -publickey $publicKey Export-ServerConfigurations -path $path -Servers $Servers -thumbprint $thumbprint -configurations $configurations -privatekey $privatekey -CertPass $certPass -serverURL $serverURL -Verbose |
Voici l’exécution du script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
PS C:\DSC> C:\DSC\Publish-ServerConfigurations - DC.ps1 Creating MOF files for DC2 VERBOSE: Create checksum file 'C:\DSC\Configurations\ServerConfig\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.meta.mof.checksum' VERBOSE: Create checksum file 'C:\DSC\Configurations\ServerConfig\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.mof.checksum' VERBOSE: Performing the operation "Move File" on target "Item: C:\DSC\Configurations\ServerConfig\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.meta.mof Destination: C:\Program Fi les\WindowsPowershell\DscService\Configuration\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.meta.mof". VERBOSE: Performing the operation "Move File" on target "Item: C:\DSC\Configurations\ServerConfig\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.meta.mof.checksum Destination: C:\P rogram Files\WindowsPowershell\DscService\Configuration\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.meta.mof.checksum". VERBOSE: Performing the operation "Move File" on target "Item: C:\DSC\Configurations\ServerConfig\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.mof Destination: C:\Program Files\W indowsPowershell\DscService\Configuration\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.mof". VERBOSE: Performing the operation "Move File" on target "Item: C:\DSC\Configurations\ServerConfig\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.mof.checksum Destination: C:\Progra m Files\WindowsPowershell\DscService\Configuration\5f9210c3-e762-4f90-9f4b-8ecbe64f6922.mof.checksum". Deploying configuration to DC2 WARNING: The 'Microsoft.PowerShell.Management' module was not imported because the 'Microsoft.PowerShell.Management' snap-in was already imported. VERBOSE: Performing the operation "Start-DscConfiguration: SendMetaConfigurationApply" on target "MSFT_DSCLocalConfigurationManager". VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendMetaConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'names paceName' = root/Microsoft/Windows/DesiredStateConfiguration'. VERBOSE: An LCM method call arrived from computer DC2 with user sid S-1-5-21-2907045379-2382782626-1225362955-500. VERBOSE: [DC2]: LCM: [ Start Set ] VERBOSE: [DC2]: LCM: [ Start Resource ] [MSFT_DSCMetaConfiguration] VERBOSE: [DC2]: LCM: [ Start Set ] [MSFT_DSCMetaConfiguration] VERBOSE: [DC2]: LCM: [ End Set ] [MSFT_DSCMetaConfiguration] in 0.0370 seconds. VERBOSE: [DC2]: LCM: [ End Resource ] [MSFT_DSCMetaConfiguration] VERBOSE: [DC2]: LCM: [ End Set ] in 0.1759 seconds. VERBOSE: Operation 'Invoke CimMethod' complete. VERBOSE: Set-DscLocalConfigurationManager finished in 0.195 seconds. |
Et voici le résultat dans Server Manager, on voit que le DC est bien installé et promu.
Par contre je remarque que DNS a été ajouté, je pense que c’est dû au fait que la partition DNS de mon domaine est intégrée dans l’Active Directory. Il faudra tester avec un domaine qui utilise un DNS externe.
Enfin voilà, les prochains DCs seront tous les mêmes, à la virgule près.
En IT c’est facile de créer mais c’est beaucoup plus difficile de détruire. Pour enlever le rôle AD on peut utiliser la cmdlet Uninstall-ADDSDomainController. Il y a toute la méta-data à nettoyer si on l’enlève de façon brutale. On doit en tout cas penser au cycle de vie complet d’un DC afin que le tout reste cohérent. Et on doit en tout cas répéter à nos collègues coté développement applicatif de ne jamais s’appuyer sur un DC donné par son nom (où pire son adresse IP). Ils doivent toujours utiliser le nom de domaine, ce sera pareil pour eux, le DC le plus proche répondra.
Et nous coté IT, on pourra faire et défaire selon besoin.