(C) Ondrej Sevecek, 2019 - www.sevecek.com, ondrej@sevecek.com
#===================== # PKGMGR and other late things DBG ("Build step: {0}" -f $MyInvocation.MyCommand.Definition) DBG "Feature installations (using PKGMGR) and other late things..." DBG ("Machine config: {0}, {1}" -f $vmName, $vmConfig.hostName) #=============== [string] $pkgmgrFeatures = $vmConfig.pkgmgr.features DBG ("Package Manager (pkgmgr) feature installation: {0} | {1} | {2}" -f (Is-ValidString $pkgmgrFeatures), (Count-MultiValue $pkgmgrFeatures), $pkgmgrFeatures) $pkgmgrAppFeatures = $vmConfig.SelectNodes('.//features[@pkgmgr]') DBG ('Applications requiring some pkgmgr features: {0}' -f (Get-CountSafe $pkgmgrAppFeatures)) if ((Get-CountSafe $pkgmgrAppFeatures) -ge 1) { foreach ($onePkgmgrAppFeatures in $pkgmgrAppFeatures) { DBG ('Adding one application pkgmgr installation into the list: {0}' -f $onePkgmgrAppFeatures.pkgmgr) $pkgmgrFeatures = Add-MultiValue $pkgmgrFeatures (Split-MultiValue $onePkgmgrAppFeatures.pkgmgr) -unique $true } } DBG ("Package Manager (pkgmgr) feature installation final list: {0} | {1} | {2}" -f (Is-ValidString $pkgmgrFeatures), (Count-MultiValue $pkgmgrFeatures), $pkgmgrFeatures) if (Is-ValidString $pkgmgrFeatures) { [string[]] $features = (Split-MultiValue $pkgmgrFeatures) | ? { $_ -notlike '*-SevecekInstallOnly' } DBG ("Features to be installed: #{0} = {1} (original: {2})" -f (Get-CountSafe $features), ($features -join ','), $pkgmgrFeatures) if (($thisOSVersionNumber -ge 6.2) -and ((Contains-Safe $features 'NetFx3') -or (Contains-Safe $features 'NetFx3ServerFeatures'))) { DBG ("Doing 6.2+ NetFx3 installation.") DBG ('Install NetFx3 from source media separately') $netFx3Source = Get-WindowsSXSOfflineMedia $global:installMediaVolume $global:installISOVolume if ($thisOSRole -notlike '*workstation*') { Run-Process 'DISM' ('/online /enable-feature /featurename:NetFx3ServerFeatures /source:"{0}"' -f $netFx3Source) } Run-Process 'DISM' ('/online /enable-feature /featurename:NetFx3 /source:"{0}"' -f $netFx3Source) } if ($thisOSVersionNumber -ge 6.2) { DBG ('Verify the feature list first') DBGSTART [string[]] $availablePkgmgrFeatures = dism /online /get-features | % { [regex]::Match($_, '\AFeature Name \: ([a-zA-Z0-9\-]+)\Z').Groups[1].Value } | ? { Is-ValidString $_ } DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ('The number of features available: #{0}' -f (Get-CountSafe $availablePkgmgrFeatures)) DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $availablePkgmgrFeatures) -lt 10 } if ((Get-CountSafe $availablePkgmgrFeatures) -ge 10) { foreach ($onePkgmgrFeatureToEnable in $features) { DBGIF ('The requested PKGMGR feature is not available: {0}' -f $onePkgmgrFeatureToEnable) { -not (Contains-Safe $availablePkgmgrFeatures $onePkgmgrFeatureToEnable) } } } } $featureList = $features -join ';' $logFile = Get-DataFileApp "pkgmgr" $null '.log' DBG ("Doing PKGMGR installation. Log: {0}" -f $logFile) Run-Process "pkgmgr" "/norestart /quiet /l:`"$logFile`" /iu:$featureList" ## ReDisable-SystemRestore ReDisable-Updates } #=============== $pkgmgrRemove = $vmConfig.pkgmgr.remove DBG ("Package Manager feature removal: {0}" -f (Is-ValidString($pkgmgrRemove))) if (Is-ValidString($pkgmgrRemove)) { $features = Split-MultiValue($pkgmgrRemove) DBG ("Features to be removed: #{0} = {1}" -f $features.Count, $pkgmgrRemove) $featureList = $features -join ';' $logFile = Get-DataFileApp 'pkgmgrRemove' $null '.log' DBG ('Doing PKGMGR removal. Log: {0}' -f $logFile) Run-Process "pkgmgr" "/norestart /quiet /l:`"$logFile`" /uu:$featureList" ## ReDisable-SystemRestore ReDisable-Updates } #===================== DBG ('Install newest possible Windows Management Framework: {0}' -f (Parse-BoolSafe $vmConfig.install.latestWmf)) if (Parse-BoolSafe $vmConfig.install.latestWmf) { $wmfBasePath = Join-Path $global:installMediaVolume 'WindowsMamagementFramework' DBGIF ('Invalid WMF base install path: {0}' -f $wmfBasePath) { -not (Test-Path $wmfBasePath) } $wmfBasePath = Join-Path $wmfBasePath ('win-{0}-{1}' -f $thisOSVersionnumber.ToString('N1', ([System.Globalization.CultureInfo]::InvariantCulture)), $global:thisOSArchitecture) DBG ('Do we have a newer installation WMF sources for this OS: {0} | {1}' -f $wmfBasePath, (Test-Path $wmfBasePath)) DBGIF ('Cannot install newer WMF version when installation package does not exist: {0}' -f $wmfBasePath) { -not (Test-Path $wmfBasePath) } if (Test-Path $wmfBasePath) { $wmfMSU = $null $wmfMSU = Get-ChildItem $wmfBasePath -Force | ? { $_.FullName -like '*.msu' } | sort LastWriteTime | select -Expand FullName | select -Last 1 DBG ('Found latest WMF installation package: {0}' -f $wmfMSU) DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $wmfMSU } if (Is-ValidString $wmfMSU) { $wusaRes = Run-Process 'WUSA' ('"{0}" /quiet /norestart' -f $wmfMSU) -returnExitCode $true Repair-PsReadLineMissing # Note: 0x00240006 = already installed if (($wusaRes -eq 0x00240005) -or ($wusaRes -eq $global:win32_ERROR_SUCCESS_REBOOT_REQUIRED)) { DBG ('Windows update installation asks for a restart, so do it and rerun after reboot.') $global:restartCurrentPhase = $true exit } DBGIF ('WUSA installation of WMF returned unexpected error: {0}' -f $wusaRes) { $wusaRes -ne 0x00240006 } } } ## ReDisable-SystemRestore ReDisable-Updates } #====================== DBG ("Local NTFS folders and shares.") $localFSs = $vmConfig.SelectNodes('./*/fs[@instance="local"]') DBG ("Found {0} local NTFS folders" -f $localFSs.Count) foreach ($oneFldr in $localFSs) { $oneFldrPath = Resolve-VolumePath $oneFldr.dir Create-NTFSFolderAndShare $oneFldrPath $oneFldr.share $oneFldr.dacl $oneFldr.quota -exists (Parse-BoolSafe $oneFldr.exists) -adminFullEveryoneChange (Parse-BoolSafe $oneFldr.everyoneChangeOnly) # Note: mind we are doing the same code once again for remote instances later DBG ('Should publish in IIS: {0} | {1} | {2}' -f (Is-ValidString $oneFldr.iis), $oneFldr.iis, $oneFldr.dns) if (Is-ValidString $oneFldr.iis) { $dnsAlias = Strip-ValueFlags $oneFldr.dns $iisVD = Strip-ValueFlags $oneFldr.iis DBG ('Flags: dnsAlias = {0} | iisVD = {1} | iis = {2}' -f $dnsAlias, $iisVD, $oneFldr.iis) DBG ('Additional IIS params: upload = {0} | maxAge = {1} | highBit = {2} | doubleEsc = {3}' -f $oneFldr.iisUploadSize, $oneFldr.iisMaxAge, $oneFldr.iisHighBit, $oneFldr.iisDoubleEsc) ############################################### ############################################### ## SEE BELOW, THERE IS THIS BLOCK ONCE AGAIN ## ############################################### ############################################### if (Has-ValueFlags $oneFldr.iis S) { DBG ('Publish into a separate web site') Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias (Join-Path $env:SystemDrive\inetpub $dnsAlias.Replace('.', '-')) $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge } else { DBG ('Publish into the Default Web Site') Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias $null $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge } } DBG ('Should define NTLM alias: {0} | {1}' -f (Is-ValidString $oneFldr.dns), $oneFldr.dns) if (Is-ValidString $oneFldr.dns) { Add-BackConnectionHostName (Strip-ValueFlags $oneFldr.dns) } if (Parse-BoolSafe $oneFldr.copySample) { $sampleSourcePath = Join-Path $global:rootDir 'IIS\sampleSMB' DBG ('Will copy sample files: {0} | {1}' -f $sampleSourcePath, $oneFldrPath) DBGSTART Copy-Item -Path $sampleSourcePath -Destination $oneFldrPath -Recurse -Force DBGER $MyInvocation.MyCommand.Name $error DBGEND } } #====================== DBG ("File Server: {0}" -f $vmConfig.fs.instance) if (Is-ValidString $vmConfig.fs.instance) { $fsRoot = Resolve-VolumePath $vmConfig.fs.root [string] $fsAdminsAce = [string]::Empty if (Is-ValidString $vmConfig.fs.app.aGroup) { $fsAdminsAce = 'F${0}@{1}' -f $vmConfig.fs.app.aGroup, $vmConfig.fs.app.iDomain DBG ('We have some FS admins group ACE to define: {0}' -f $fsAdminsAce) } Create-NTFSFolderAndShare $fsRoot $null $fsAdminsAce $null DBG ('Searching for shares requested in this instance: {0}' -f $vmConfig.fs.instance) $fsShares = $xmlConfig.SelectNodes('./VMs/MACHINE[vm/@do="true"]/*/fs[translate(@instance,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]' -f $vmConfig.fs.instance.ToLower()) DBG ('Found shares: {0}' -f (Get-CountSafe $fsShares)) foreach ($oneFldr in $fsShares) { $oneFldrPath = Join-Path $fsRoot $oneFldr.dir Create-NTFSFolderAndShare $oneFldrPath $oneFldr.share ('{0}|{1}' -f $fsAdminsAce, $oneFldr.dacl) $oneFldr.quota -adminFullEveryoneChange (Parse-BoolSafe $oneFldr.everyoneChangeOnly) # Note: really, just a plain copy/paste from above DBG ('Should publish in IIS: {0} | {1} | {2}' -f (Is-ValidString $oneFldr.iis), $oneFldr.iis, $oneFldr.dns) if (Is-ValidString $oneFldr.iis) { $dnsAlias = Strip-ValueFlags $oneFldr.dns $iisVD = Strip-ValueFlags $oneFldr.iis DBG ('Flags: dnsAlias = {0} | iisVD = {1} | iis = {2}' -f $dnsAlias, $iisVD, $oneFldr.iis) DBG ('Additional IIS params: upload = {0} | maxAge = {1} | highBit = {2} | doubleEsc = {3}' -f $oneFldr.iisUploadSize, $oneFldr.iisMaxAge, $oneFldr.iisHighBit, $oneFldr.iisDoubleEsc) ############################################### ############################################### ## SEE ABOVE, THERE IS THIS BLOCK ONCE AGAIN ## ############################################### ############################################### if (Has-ValueFlags $oneFldr.iis S) { DBG ('Publish into a separate web site') Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias (Join-Path $env:SystemDrive\inetpub $dnsAlias.Replace('.', '-')) $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge } else { DBG ('Publish into the Default Web Site') Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias $null $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge } } <# DBG ('Should publish in IIS: {0} | {1} | {2}' -f (Is-ValidString $oneFldr.iis), $oneFldr.iis, $oneFldr.dns) if (Is-ValidString $oneFldr.iis) { Publish-FSinIIS $oneFldrPath $oneFldr.iis (Strip-ValueFlags $oneFldr.dns) } #> DBG ('Should define NTLM alias: {0} | {1}' -f (Is-ValidString $oneFldr.dns), $oneFldr.dns) if (Is-ValidString $oneFldr.dns) { Add-BackConnectionHostName (Strip-ValueFlags $oneFldr.dns) } if (Parse-BoolSafe $oneFldr.copySample) { $sampleSourcePath = Join-Path $global:rootDir 'IIS\sampleSMB' DBG ('Will copy sample files: {0} | {1}' -f $sampleSourcePath, $oneFldrPath) DBGSTART Get-ChildItem $sampleSourcePath | Copy-Item -Destination $oneFldrPath -Recurse -Force DBGER $MyInvocation.MyCommand.Name $error DBGEND } } } #========================= DBG ('Install NLB: {0}' -f (Is-ValidString $vmConfig.nlb.instance)) if (Is-ValidString $vmConfig.nlb.instance) { DBG ('Will install NLB on the following IP: {0} | {1}' -f $vmConfig.nlb.instance, $vmConfig.nlb.ip) } #========================= DBG ("DNS Server configuration: {0}" -f (Is-ValidString $vmConfig.dns.name)) if (Is-ValidString $vmConfig.dns.name) { #========================= DBG ("Setting DNS forwarders: {0}" -f (Is-ValidString $vmConfig.dns.forwarders)) if (Is-ValidString($vmConfig.dns.forwarders)) { if ($vmConfig.dns.forwarders -eq $emptyValueMarker) { DBG ("Resetting forwarders to NONE.") Run-Process 'DNSCMD' '/ResetForwarders' } else { $dnsForwarders = Split-MultiValue $vmConfig.dns.forwarders DBG ("Resetting forwarders to: {0}x = {1}" -f $dnsForwarders.Count, $vmConfig.dns.forwarders) Run-Process 'DNSCMD' ('/ResetForwarders {0}' -f ($dnsForwarders -join ' ')) } } #========================= DBG ('Conditional forwarders: #{0}' -f (Get-CountSafe $vmConfig.dns.fwd)) if ((Get-CountSafe $vmConfig.dns.fwd) -gt 0) { $vmConfig.dns.fwd | % { $oneFwd = $_ $cmdSwitches = '' $fwdIPs = (Split-MultiValue $oneFwd.ip) -join ' ' if ($oneFwd.type -eq 'F') { $cmdSwitches = '/ZoneAdd {0} /DsForwarder {1} /DP /Forest' -f $oneFwd.zone, $fwdIPs } if ($oneFwd.type -eq 'O') { $cmdSwitches = '/ZoneAdd {0} /DsForwarder {1} /DP /Domain' -f $oneFwd.zone, $fwdIPs } if ($oneFwd.type -eq 'L') { $cmdSwitches = '/ZoneAdd {0} /Forwarder {1}' -f $oneFwd.zone, $fwdIPs } Run-Process 'DNSCMD' ('localhost {0}' -f $cmdSwitches) } } #========================= DBG ("Additional DNS zones: {0}" -f (Is-ValidString $vmConfig.dns.zones)) if (Is-ValidString $vmConfig.dns.zones) { $dnsZones = Split-MultiValue $vmConfig.dns.zones DBG ("DNS Zones to be created: {0}x | {1}" -f $dnsZones.Count, (($dnsZones -join ', ') | Out-String)) $dnsZones | % { $oneZone = $_ DBG ('One DNS zone for processing: {0}' -f $oneZone) Create-DNSZone $oneZone } } #====================== $dnsRecords = $vmConfig.dns.SelectNodes('./rr') DBG ('Manual DNS records: {0}' -f (Get-CountSafe $dnsRecords)) $dnsRecords | % { Add-DNSRecord $_.type $_.name $_.zone $_.value } #====================== # Note: sphere - if there is a split-dns, then it does not ever have more than two spheres # an internal sphere which is inside to the intranet and a public DNS server # If we run more intranets, there never be the same zone in more intranets and/or public DNS servers [string] $dnsSphere = $vmConfig.dns.sphere # Note: we cannot call .ToLower() on possibly $null object so we wait until it is cast to [string] $dnsSphere = $dnsSphere.ToLower() [bool] $areWeRODC = Is-LocalComputerDomainController -rodcOnly $true DBG ('Current DNS sphere determined: {0} | rodc = {1}' -f $dnsSphere, $areWeRODC) #====================== # Note: this is the old simple method to create @dns="" record # which has been obsoleted by the newtechnology below DBG ('Application OBSOLETE DNS records: sphere = {0}' -f $dnsSphere) if ($dnsSphere -eq 'intranet') { # Note: on Windows 2000, the ZoneType is 0/1 for file/AD primary zones # since Windows 2003, the ZoneType is only 1 regardless its file or AD nature $localPrimaryDNSZones = Get-WmiQueryArray '.' 'SELECT * FROM MicrosoftDNS_Zone WHERE ZoneType = 0 OR ZoneType = 1' 'root\MicrosoftDNS' if ($localPrimaryDNSZones.Count -gt 0) { DBG ('We have some primary DNS zones here.') [string] $dnsZoneFilter = '' foreach ($oneLocalPrimaryDNSZone in $localPrimaryDNSZones) { if (Is-DnsZoneUpdatable $oneLocalPrimaryDNSZone) { DBG ('Adding XPath filter for primary DNS zone: {0}' -f $oneLocalPrimaryDNSZone.Name) # Note that there may be more DNS names in the @dns MultiValue if (Is-ValidString $dnsZoneFilter) { $dnsZoneFilter += ' or substring-before(translate(@dns,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"), ".{0}")' -f $oneLocalPrimaryDNSZone.Name.ToLower() } else { $dnsZoneFilter += 'substring-before(translate(@dns,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"), ".{0}")' -f $oneLocalPrimaryDNSZone.Name.ToLower() } } } $dnsZoneFilter = './VMs/MACHINE[vm/@do="true"]/*/*[{0}]' -f $dnsZoneFilter DBG ('Final XPath for local app dns records: {0}' -f $dnsZoneFilter) $dnsApps = $xmlConfig.SelectNodes($dnsZoneFilter) DBG ("Found {0} application installations that require OBSOLETE DNS records" -f (Get-CountSafe $dnsApps)) if ((Get-CountSafe $dnsApps) -gt 0) { foreach ($oneApp in $dnsApps) { $dnsRecords = Split-MultiValue $oneApp.dns $oneAppResolved = Resolve-DNSNameWithConfig $null $oneApp DBGIF ("Application requires OBSOLETE DNS records: machine = {0} | node = {1} | cname = {2} | {3}" -f (Get-MachineFQDN $oneApp), $oneApp.psbase.ParentNode.name, $oneAppResolved.cnameFQDN, ($dnsRecords -join ',')) { $true } foreach ($oneRecord in $dnsRecords) { if (Has-ValueFlags $oneRecord C) { $recType = 'CNAME' $recData = '{0}.' -f $oneAppResolved.cnameFQDN } if (Has-ValueFlags $oneRecord A) { $recType = 'A' $recData = $oneAppResolved.ipAddress } $oneRecordPure = Strip-ValueFlags $oneRecord $firstDotIndex = $oneRecordPure.IndexOf('.') $recZone = $oneRecordPure.SubString($firstDotIndex + 1) $recName = $oneRecordPure.SubString(0, $firstDotIndex) Add-DNSRecord $recType $recName $recZone $recData } } } } } Wait-IfRequested dnsrr | Out-Null DBG ('Application requested NewAge DNS zones to be created (with @createAt): sphere = {0}' -f $dnsSphere) # Note: the @createAt is used here instead of @instance because we do not mark DNS servers with @instance # value of @createAt will usually be the machine domain name, but not always necessarily $appDnsZones = $xmlConfig.SelectNodes('./VMs/MACHINE[vm/@do="true"]//dnsrr[translate(@sphere,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}" and @createAt]' -f $dnsSphere) DBG ('Found application NewAge DNS zones to be created (with @createAt): {0} | areWeRODC = {1}' -f (Get-CountSafe $appDnsZones), $areWeRODC) if ((Get-CountSafe $appDnsZones) -gt 0) { foreach ($oneAppDnsZone in $appDnsZones) { [string] $createAtMarker = $oneAppDnsZone.createAt.Trim('.') DBG ('One app NewAge DNS zone should be created at: zone = {0} | at = {1} | atNormalized = {2}' -f $oneAppDnsZone.zone, $oneAppDnsZone.createAt, $createAtMarker) # Note: the ZoneType member for primary DNS zone is 0 on Windows 2000 while is 1 on Windows 2003 and newer # for example conditional forwarder has ZoneType = 4 DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 5.2 } $markerZoneExists = Get-WMIQuerySingleObject '.' ('SELECT * FROM MicrosoftDNS_Zone WHERE Name = "{0}" AND (ZoneType = 0 OR ZoneType = 1)' -f $createAtMarker) -namespace 'root\MicrosoftDNS' if ((Is-NonNull $markerZoneExists) -and (Is-DnsZoneUpdatable $markerZoneExists)){ [string] $oneAppDnsZoneZoneName = (Strip-ValueFlags $oneAppDnsZone.zone).Trim('.').ToLower() DBG ('Marker zone found, will create the application requested zone if not existing: {0} | {1}' -f $oneAppDnsZone.zone, $oneAppDnsZoneZoneName) $oneAppDnsZoneExists = Get-WMIQuerySingleObject '.' ('SELECT * FROM MicrosoftDNS_Zone WHERE Name = "{0}"' -f $oneAppDnsZoneZoneName) -namespace 'root\MicrosoftDNS' if (Is-Null $oneAppDnsZoneExists) { DBG ('The requested zone does not exist, create: {0} | {1}' -f $oneAppDnsZoneZoneName, $oneAppDnsZone.zone) # Note: this is correct, we must pass the flags down as well Create-DNSZone $oneAppDnsZone.zone } } } } DBG ('Application requested NewAge DNS records to be created: sphere = {0}' -f $dnsSphere) $appDnsRecords = $xmlConfig.SelectNodes('./VMs/MACHINE[vm/@do="true"]//dnsrr[translate(@sphere,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]' -f $dnsSphere) DBG ('Found application NewAge DNS records (dnsrr) to be created: {0}' -f (Get-CountSafe $appDnsRecords)) if ((Get-CountSafe $appDnsRecords) -gt 0) { foreach ($oneAppDnsRecord in $appDnsRecords) { $ownerZoneName = (Strip-ValueFlags $oneAppDnsRecord.zone).Trim('.') DBG ('One app NewAge DNS record (one dnsrr) should be created at: zoneNormalized = {0} | zone = {1} | record = {2} | type = {3}' -f $ownerZoneName, $oneAppDnsRecord.zone, $oneAppDnsRecord.rec, $oneAppDnsRecord.type) # Note: MicrosoftDNS_Zone.ZoneType since Windows 2003 values: # 1 = primary zone # 2 = secondary zone (we do not go into creating records in secondary zones apparently :-) # 3 = stub zone # 4 = conditional forwarder $ownerZoneExists = Get-WMIQuerySingleObject '.' ('SELECT * FROM MicrosoftDNS_Zone WHERE Name = "{0}" AND (ZoneType = 0 OR ZoneType = 1)' -f $ownerZoneName) -namespace 'root\MicrosoftDNS' if ((Is-NonNull $ownerZoneExists) -and (Is-DnsZoneUpdatable $ownerZoneExists)) { [string] $newRecordTypeName = 'UNSUPPORTED' switch ($oneAppDnsRecord.type) { 'A' { $newRecordTypeName = 'A' } 'C' { $newRecordTypeName = 'CNAME' } 'CNAME' { $newRecordTypeName = 'CNAME' } 'S' { $newRecordTypeName = 'SRV' } 'SRV' { $newRecordTypeName = 'SRV' } 'T' { $newRecordTypeName = 'TXT' } 'TXT' { $newRecordTypeName = 'TXT' } 'M' { $newRecordTypeName = 'MX' } 'MX' { $newRecordTypeName = 'MX' } default { DBGIF ('Unsupported RR: {0}' -f $oneAppDnsRecord.type) { $true } } } [string] $newRecordName = $oneAppDnsRecord.rec.Trim('.') [string] $newRecordFullName = ('{0}.{1}' -f $newRecordName, $ownerZoneName).Trim('.') # # [string] $newRecordValue = $oneAppDnsRecord.value if (Is-ValidString $oneAppDnsRecord.ref) { # Note: we may have several instances of a single service on a machine # and each instance may use a different NLB/CLUSTER for example DBG ('The record value is referencing: {0}' -f $oneAppDnsRecord.ref) [string[]] $refTokens = Split-MultiValue $oneAppDnsRecord.ref [string] $refType = $refTokens[0] DBGIF ('Weird reference type: {0}' -f $refType) { -not (Contains-Safe @('ip', 'fqdn', 'nlb') $refType) } [System.Xml.XmlElement] $referedMachine = $null [System.Xml.XmlElement] $referedSvc = $null if ($refTokens.Length -lt 2) { DBG ('The DNS record is refering to the local machine') $referedMachine = Get-MachineFromElement $oneAppDnsRecord $referedSvc = Get-SvcFromElement $oneAppDnsRecord } else { DBGIF $MyInvocation.MyCommand.Name { $refTokens.Length -lt 3 } [string] $referedSvcType = $refTokens[1] [string] $referedInstance = $refTokens[2] DBG ('The DNS record is refering to a machine hosting a service instance: {0} | {1}' -f $referedSvcType, $referedInstance) if ($referedInstance -eq 'local') { DBG ('The DNS record is refering to a local service') $referedMachine = Get-MachineFromElement $oneAppDnsRecord $referedSvc = Get-SvcFromElement $oneAppDnsRecord } else { DBG ('The DNS record is refering to possibly a foreign service') $referedSvc = Get-FirstAppHostInInstance -appTag $referedSvcType -instance $referedInstance -hostType svcConfig $referedMachine = Get-MachineFromElement $referedSvc } } DBG ('Refered machine determined: {0}' -f $referedMachine.hostName) DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedMachine.hostName } DBG ('Refered svc determined: {0} | {1}' -f $referedSvc.psbase.Name, $referedSvc.instance) DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedSvc.psbase.Name } # Note: not all services must have an instance specified #DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedSvc.instance } switch ($refType) { 'ip' { DBG ('Get the IP address for the refered machine') [object[]] $referedMachineIPs = $null $referedMachineIPs = Get-NetworkMap $referedMachine.hostName | ? { Is-EmptyString $_.nlbInstance } DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $referedMachineIPs) -ne 1 } $newRecordValue = $referedMachineIPs[0].ipAddress } 'fqdn' { DBG ('Get the FQDN for the refered machine') $newRecordValue = Get-MachineFQDN $referedMachine } 'nlb' { DBG ('Get the NLB IP address for the refered service: {0}' -f $referedSvc.psbase.Name) if ($referedSvc.psbase.Name -eq 'nlb') { $referedNlbInstance = $referedSvc.instance } else { [System.Xml.XmlElement] $referedSvcNlbRule = $null $referedSvcNlbRule = $referedSvc.SelectSingleNode('./nlbrule') DBGIF $MyInvocation.MyCommand.Name { Is-Null $referedSvcNlbRule } $referedNlbInstance = $referedSvcNlbRule.instance } DBG ('Refered NLB instance: {0}' -f $referedNlbInstance) DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedNlbInstance } [object[]] $referedMachineNlb = $null $referedMachineNlb = Get-NetworkMap $referedMachine.hostName | ? { $_.nlbInstance -eq $referedNlbInstance } DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $referedMachineNlb) -ne 1 } $newRecordValue = $referedMachineNlb[0].ipAddress } default { DBGIF ('Invalid ref type') { $true } } } DBG ('Real record value determined: {0}' -f $newRecordValue) } # # DBG ('Normalize and assert final value: {0} | {1}' -f $newRecordValue, $newRecordTypeName) if ($newRecordTypeName -eq 'CNAME') { $newRecordValue = '{0}.' -f $newRecordValue.Trim('.').ToLower() } if ($newRecordTypeName -eq 'A') { $newRecordValue = $newRecordValue.Trim('.') } if (Is-EmptyString $newRecordName.Trim('.')) { DBG ('Normalize self-zone-record name') $newRecordName = '.' DBGIF ('Invalid self-zone-record type: {0}' -f $newRecordTypeName) { $newRecordTypeName -ne 'A' } } DBGIF $MyInvocation.MyCommand.Name { ($newRecordTypeName -eq 'CNAME') -and (Is-IPv4OrIPv6Address $newRecordValue) } DBGIF $MyInvocation.MyCommand.Name { ($newRecordTypeName -eq 'A') -and (-not (Is-IPv4OrIPv6Address $newRecordValue)) } DBGIF $MyInvocation.MyCommand.Name { ($newRecordTypeName -ne 'A') -and ($newRecordValue -eq '.') } # # DBG ('Verify whether the exact record already exists: {0} | {1} | {2} | {3}' -f $ownerZoneName, $newRecordName, $newRecordFullName, $newRecordTypeName, $newRecordValue) $rrExactExists = Get-WmiQueryArray '.' ('SELECT * FROM MicrosoftDNS_ResourceRecord WHERE __CLASS = "MicrosoftDNS_{0}Type" AND ContainerName = "{1}" AND OwnerName = "{2}" AND RecordData = "{3}"' -f $newRecordTypeName, $ownerZoneName, $newRecordFullName, $newRecordValue) -namespace 'root\MicrosoftDNS' if ((Get-CountSafe $rrExactExists) -eq 0) { DBG ('Verify whether a similar record already exists: {0} | {1} | {2} | {3}' -f $ownerZoneName, $newRecordName, $newRecordFullName, $newRecordTypeName) $rrSimilarExists = Get-WmiQueryArray '.' ('SELECT * FROM MicrosoftDNS_ResourceRecord WHERE __CLASS = "MicrosoftDNS_{0}Type" AND ContainerName = "{1}" AND OwnerName = "{2}"' -f $newRecordTypeName, $ownerZoneName, $newRecordFullName) -namespace 'root\MicrosoftDNS' DBGIF ("Similar but not the same DNS resource records already exist: {0} | -->`r`n{1}`r`n{2}" -f $newRecordFullName, ($rrSimilarExists | Out-String), ($rrExactExists | Out-String)) { (Get-CountSafe $rrSimilarExists) -gt 0 } Add-DNSRecord -type $newRecordTypeName -name $newRecordName -zone $ownerZoneName -value $newRecordValue } } } } #================ DBG ("Restarting DNS server service to finish its configuration") Restart-Service -Name DNS -EV er -EA SilentlyContinue DBGER $MyInvocation.MyCommand.Name $er } $winRMPath = Join-Path $env:windir 'System32\winrm.vbs' DBG ('WINRM script exists: {0} | {1}' -f (Test-Path $winRMPath), $winRMPath) #================== DBG ('Should we enable WINRM server remote access here? {0}' -f (Parse-BoolSafe $vmConfig.winrm.enable)) if (Parse-BoolSafe $vmConfig.winrm.enable) { DBGIF 'WinRM already enabled for remote access' { ($global:thisOSVersionNumber -ge 6.2) } Run-Process 'cscript' ('"{0}" qc -quiet' -f $winRMPath) } #================== DBG ('Should we enable WINRM client? {0}' -f (Parse-BoolSafe $vmConfig.wks.winrm.enable)) if (Parse-BoolSafe $vmConfig.wks.winrm.enable) { $winRmservice = Get-WmiQuerySingleObject '.' 'SELECT * FROM Win32_Service WHERE Name = "winrm"' DBGIF 'WinRM client already enabled' { $winRmservice.State -eq 'Running' } DBGIF 'WinRM client already enabled' { $winRmservice.StartMode -eq 'Auto' } if ($winRmservice.StartMode -ne 'Auto') { DBG ('Enable WINRM service to start Auto') DBGSTART $wmiRs = $null $wmiRs = $winRmservice.ChangeStartMode('Automatic') DBGER $error DBGEND DBGWMI $wmiRs } if ($winRmservice.State -ne 'Running') { DBG ('Start WINRM service') DBGSTART Start-Service winrm DBGER $error DBGEND } } DBG ('Should we enable some authentication methods for WINRM? {0}' -f ((Is-ValidString $vmConfig.winrm.auth) -or (Is-ValidString $vmConfig.wks.winrm.auth))) if ((Is-ValidString $vmConfig.winrm.auth) -or (Is-ValidString $vmConfig.wks.winrm.auth)) { DBG ('Get current WinRM config first') Run-Process 'cscript' ('"{0}" get winrm/config' -f $winRMPath) DBGIF $MyInvocation.MyCommand.Name { (Get-Service winrm).Status -ne 'Running' } DBGIF $MyInvocation.MyCommand.Name { (Get-WmiQuerySingleObject '.' 'SELECT * FROM Win32_Service WHERE Name = "winrm"').StartMode -ne 'Auto' } DBG ('WinRM server authentication methods to enable: {0}' -f $vmConfig.winrm.auth) if (Is-ValidString $vmConfig.winrm.auth) { foreach ($oneWinrmAuth in (Split-MultiValue $vmConfig.winrm.auth)) { DBG ('Will enable WinRM server authentication: {0}' -f $oneWinrmAuth) Run-Process 'cscript' "`"$winRMPath`" set winrm/config/service/auth @{$oneWinrmAuth=`"true`"} " } } DBG ('WinRM client authentication methods to enable: {0}' -f $vmConfig.wks.winrm.auth) if (Is-ValidString $vmConfig.wks.winrm.auth) { foreach ($oneWinrmAuth in (Split-MultiValue $vmConfig.wks.winrm.auth)) { DBG ('Will enable WinRM client authentication: {0}' -f $oneWinrmAuth) Run-Process 'cscript' "`"$winRMPath`" set winrm/config/client/auth @{$oneWinrmAuth=`"true`"} " if ($oneWinrmAuth -eq 'CredSSP') { DBG ('Should the client have a CredSSP access to some instances: {0} = {1}' -f (Is-ValidString $vmConfig.wks.winrm.instances), $vmConfig.wks.winrm.instances) foreach ($oneCredSSPInstance in (Split-MultiValue $vmConfig.wks.winrm.instances)) { $oneCredSSPInstName = Strip-ValueFlags $oneCredSSPInstance $oneCredSSPInstType = Get-ValueFlags $oneCredSSPInstance $credSSPFQDNs = Get-AllAppHostsOfInstance $oneCredSSPInstType $oneCredSSPInstName fqdn DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $credSSPFQDNs) -lt 1 } foreach ($oneCredFQDN in $credSSPFQDNs) { Allow-CredentialsDelegation fresh "wsman/$oneCredFQDN" DBGIF $MyInvocation.MyCommand.Name { $oneCredFQDN.IndexOf('.') -lt 1 } Allow-CredentialsDelegation fresh ('wsman/{0}' -f $oneCredFQDN.SubString(0, ($oneCredFQDN.IndexOf('.')))) } } DBG ('Should the client have a CredSSP access to some other machines: {0} = {1}' -f (Is-ValidString $vmConfig.wks.winrm.machines), $vmConfig.wks.winrm.machines) foreach ($oneCredSSPMachine in (Split-MultiValue $vmConfig.wks.winrm.machines)) { Allow-CredentialsDelegation fresh "wsman/$oneCredSSPMachine" } } } } } DBG ('Should we enable PS Remoting: {0}' -f (Parse-BoolSafe $vmConfig.winrm.psremoting)) if (Parse-BoolSafe $vmConfig.winrm.psremoting) { [string] $tmpOutString = $null if ($PSVersionTable['PSVersion'].Major -gt 2) { DBG ('Enabling psremoting without network profile checking on PS 3.0+') DBGSTART $tmpOutString = Enable-PSRemoting -Force -Confirm:$false -SkipNetworkProfileCheck | Out-String DBGER $MyInvocation.MyCommand.Name $error DBGEND } else { DBG ('Enabling psremoting with network profile checking on older PS 2.0') DBGSTART $tmpOutString = Enable-PSRemoting -Force -Confirm:$false | Out-String DBGER $MyInvocation.MyCommand.Name $error DBGEND } DBG ('Enabling PS remoting results: {0}' -f $tmpOutString) } DBG ('Should we add any WINRM TrustedHosts? {0}' -f (Is-ValidString $vmConfig.wks.winrm.trustedHosts)) if (Is-ValidString $vmConfig.wks.winrm.trustedHosts) { DBG ('Get the current list of trusted hosts') DBGSTART $currentTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value DBGEND $newTrustedHosts = (Split-MultiValue $vmConfig.wks.winrm.trustedHosts) -join ',' if (Is-ValidString $currentTrustedHosts) { $resultingTrustedHosts = '{0},{1}' -f $currentTrustedHosts.Trim(','), $newTrustedHosts } else { $resultingTrustedHosts = $newTrustedHosts } DBG ('Enabling WinRM trusted hosts: orig = {0} | new = {1} | set = {2}' -f $currentTrustedHosts, $newTrustedHosts, $resultingTrustedHosts) DBGSTART Set-Item WSMan:\localhost\Client\TrustedHosts -Value $resultingTrustedHosts -Force DBGER $MyInvocation.MyCommand.Name $error DBGEND } # # # # if (Is-FirstForestDC $vmConfig) { DBG ('Prepare the finalization machinery, only here after FS is ready as well as DNS') [System.Collections.ArrayList] $deList = @() $rootDSE = Get-DE 'RootDSE' ([ref] $deList) $domainDN = GDES $rootDSE defaultNamingContext $domainDE = Get-DE $domainDN ([ref] $deList) $configDN = GDES $rootDSE configurationNamingContext $configDE = Get-DE $configDN ([ref] $deList) $domainFQDN = Get-NCName $domainDN 'dNSRoot' $myForestMachines = Get-ForestMachines $domainFQDN # # DBG ('Finalization container in configuration partition') $finalCntDnPath = New-Object System.Collections.ArrayList Add-DNComponent $finalCntDnPath 'CN=Services' 'container' -rootDN $configDN #Add-DNComponent $finalCntDnPath 'CN=Sevecek VM Buildup' 'container' $null '/N /I:T$Domain Admins:GA|/I:T$Authenticated Users:GR|Authenticated Users:CC;sevecekVMBuilderTaskContainer|-' # Note: we must configure the object with Administrators group instead of Domain Admins because some actions on DCs are running under install accounts which are made members # of Administrators group only and are not put into Domain Admins explicitly (an example is RDP role builder script) Add-DNComponent $finalCntDnPath 'CN=Sevecek VM Buildup' 'container' $null '/N /I:T$Administrators:GA|/I:T$Authenticated Users:GR|-' $finalCntDE = Create-DNPath $finalCntDnPath $configDE ([ref] $deList) # # foreach ($oneMyForestMachine in $myForestMachines) { if ($oneMyForestMachine.do) { DBG ('Finalization machine object: {0}' -f $oneMyForestMachine.fqdn) $finalMachineDnPath = New-Object System.Collections.ArrayList if ($oneMyForestMachine.domain -ne $domainFQDN) { # We cannot use the joinerCred to delegate write access since subdomain accounts are not yet defined #Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null 'Authenticated Users:RPWP;sevecek-VMB-MachineFinished' (GDES $finalCntDE distinguishedName) Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null 'Authenticated Users:RPWP;Sevecek VMBuilder Task Attributes' (GDES $finalCntDE distinguishedName) } elseif (Is-ValidString $oneMyForestMachine.joinerCred) { # Not forest root domain first DC case #Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null ('{0}:RPWP;sevecek-VMB-MachineFinished' -f $oneMyForestMachine.joinerCred) (GDES $finalCntDE distinguishedName) Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null ('{0}:RPWP;Sevecek VMBuilder Task Attributes' -f $oneMyForestMachine.joinerCred) (GDES $finalCntDE distinguishedName) } else { Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null $null (GDES $finalCntDE distinguishedName) } [ADSI] $createdMachineDE = $null $createdMachineDE = (Create-DNPath $finalMachineDnPath $finalCntDE ([ref] $deList)) DBG ('Set some basic machine parameters: {0}' -f $oneMyForestMachine.hostName) DBGSTART $createdMachineDE.Put('sevecek-VMB-Hostname', $oneMyForestMachine.hostName) $adsiRs = $createdMachineDE.SetInfo() DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ('ADSI Result: {0}' -f ($adsiRs | Out-String)) # # DBG ('Machine finalization file container should be created as well: {0} | {1}' -f (Is-ValidString $vmConfig.finalizationShare), $oneMyForestMachine.fqdn) if (Is-ValidString $vmConfig.finalizationShare) { [string] $finalizationFile = Prepare-FinalizationFile -share $vmConfig.finalizationShare -hostName $oneMyForestMachine.hostName -domain $oneMyForestMachine.domain DBG ('Finalization machine object in the share: {0}' -f $finalizationFile) } } else { DBG ('Skipping finalization object for disabled machine: {0}' -f $oneMyForestMachine.fqdn) } } Dispose-List ([ref] $deList) } # # # # Finish-Machine $false 'BeforeTrusts' # # # # DBG ('Should we build any trusts from/to this domain') $trusts = $vmConfig.SelectNodes('./domain/trust') DBG ('Found requested trusts: {0}' -f (Get-CountSafe $trusts)) if ((Get-CountSafe $trusts) -gt 0) { # Note: just to be on the safe side DBGIF $MyInvocation.MyCommand.Name { -not (Is-FirstDomainDC $vmConfig) } if (Is-FirstDomainDC $vmConfig) { foreach ($oneTrust in $trusts) { DBG ('One trust to build: {0} | {1} | {2}' -f $oneTrust.domain, $oneTrust.type, $oneTrust.direction) # Note: works only for top level domains where @new=FQDN $trustCustomersQuery = './VMs/MACHINE[vm/@do="true"]/domain[translate(@new,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]/trust[translate(@domain,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{1}"]' -f $oneTrust.domain.ToLower(), $global:thisComputerDomain.ToLower() DBG ('Which customer has the trust with our domain: {0}' -f $trustCustomersQuery) $trustCustomers = $xmlConfig.SelectNodes($trustCustomersQuery) DBG ('Found trust customers: {0}' -f (Get-CountSafe $trustCustomers)) DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $trustCustomers) -ne 1 } if ((Get-CountSafe $trustCustomers) -eq 1) { DBG ('Get wait creds in order to wait for the remote machine several times') $waitCred = Define-JoinerCredentials $oneTrust.svc.login $oneTrust.svc.domain $oneTrust.svc.pwd ('{0}@{1}' -f $oneTrust.svc.login, $oneTrust.svc.domain) DBG ('We must first wait for the customer to finish to the same point as we did') foreach ($oneTrustCustomer in $trustCustomers) { Wait-Machine (Get-MachineWaitParams $oneTrustCustomer) -waiterCredentials $waitCred -subPhase 'BeforeTrusts' } DBG ('The customer finished. Proceed with the trust creation: {0}' -f $oneTrust.type) DBGIF $MyInvocation.MyCommand.Name { ($oneTrust.type -ne 'external') -and ($oneTrust.type -ne 'forest') } ## ## # Note: we are using the .NET trust creating which performs anonymous LSARPC lookup into the foreign forest # and fails with "access denied" if the anonymous access is not enabled # May we use NETDOM TRUST it could be better in the future [bool] $anynousNamedPipesUpdated = $false DBG ('Get the list of named pipes that have anonymous access enabled') DBGSTART [string[]] $originalAnynymousPipes = (Get-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer\Parameters -Name NullSessionPipes).NullSessionPipes DBGER $MyInvocation.MyCommand.Name $error DBGEND DBGIF ('The LSARPC named pipe does not have anonymous access enabled: {0}' -f ($originalAnynymousPipes -join ',')) { -not (Contains-Safe $originalAnynymousPipes lsarpc) } if (-not (Contains-Safe $originalAnynymousPipes lsarpc)) { DBG ('Updating the anonymous named pipe list to contain LSARPC as well') DBGSTART Set-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer\Parameters -Name NullSessionPipes -Value ($originalAnynymousPipes + 'lsarpc') -Type MultiString DBGER $MyInvocation.MyCommand.Name $error DBGEND $anynousNamedPipesUpdated = $true } ## ## Run-Process 'nslookup' ('-q=SRV _ldap._tcp.dc._msdcs.{0}' -f $oneTrust.domain) if ($oneTrust.type -eq 'external') { DBG ('Creating external trust: {0}' -f $oneTrust.direction) DBGIF $MyInvocation.MyCommand.Name { (-not (Has-MultiValue $oneTrust.direction 'inbound')) -and (-not (Has-MultiValue $oneTrust.direction 'outbound')) } DBGIF 'Name suffix routing not supported yet' { (Parse-BoolSafe $oneTrust.enableSfx) } if (Has-MultiValue $oneTrust.direction 'inbound') { Run-Process netdom ('trust {0} /domain:{1} /passwordT:{2} /Add /OneSide:trusted' -f $oneTrust.domain, $global:thisComputerDomain, $oneTrust.pwd) } if (Has-MultiValue $oneTrust.direction 'outbound') { Run-Process netdom ('trust {0} /domain:{1} /passwordT:{2} /Add /OneSide:trusting' -f $global:thisComputerDomain, $oneTrust.domain, $oneTrust.pwd) } } elseif ($oneTrust.type -eq 'forest') { DBG ('Creating forest trust') Create-LocalSideForestTrust $oneTrust.domain $oneTrust.pwd ((Split-MultiValue $oneTrust.direction) -join ',') Finish-Machine $false ('TrustCreated:{0}' -f $oneTrust.domain) DBG ('We must again wait for the customer to finish to the same point as we did') foreach ($oneTrustCustomer in $trustCustomers) { Wait-Machine (Get-MachineWaitParams $oneTrustCustomer) -waiterCredentials $waitCred -subPhase ('TrustCreated:{0}' -f $global:thisComputerDomain) } Wait-OutboundForestTrust $oneTrust.domain DBG ('Should we enable all the alternative name routing suffixes: {0}' -f $oneTrust.enableSfx) if (Parse-BoolSafe $oneTrust.enableSfx) { Enable-ForestTrustAllNameSuffixes $oneTrust.domain } Finish-Machine $false ('TrustVerified:{0}' -f $oneTrust.domain) DBG ('We must again wait for the customer to finish to the same point as we did') foreach ($oneTrustCustomer in $trustCustomers) { Wait-Machine (Get-MachineWaitParams $oneTrustCustomer) -waiterCredentials $waitCred -subPhase ('TrustVerified:{0}' -f $global:thisComputerDomain) } } ## ## if ($anynousNamedPipesUpdated) { DBG ('Removing the LSARPC from the anonymous named pipe list which we just updated before creating the trusts') DBGSTART Set-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer\Parameters -Name NullSessionPipes -Value $originalAnynymousPipes -Type MultiString DBGER $MyInvocation.MyCommand.Name $error DBGEND } } } } } # Note: small application installations # # # #===================== DBG ('Do we require smart card support: {0}' -f (Parse-BoolSafe $vmConfig.sc.gemaltoMiniDrv)) if (Parse-BoolSafe $vmConfig.sc.gemaltoMiniDrv) { if ($global:thisOSVersion -like '5.*') { DBG ('Install USBCCID driver on Windows 5.x') $usbccidDrvFolder = Join-Path $global:installMediaVolume 'DRIVERS\USBCCID\USBCCID' $usbccidDrvPath = Join-Path $usbccidDrvFolder 'usbccid.inf' Run-Process (Join-Path $env:SystemRoot 'System32\rundll32.exe') ('advpack.dll,LaunchINFSectionEx "{0}",DefaultInstall,,4N' -f $usbccidDrvPath) $false $null $null $null $true $usbccidDrvFolder if ($global:thisOSVersionNumber -eq 5.1) { DBG ('Update USBCCID driver on Windows XP') $usbccidUpdate = Join-Path $global:installMediaVolume 'DRIVERS\USBCCID\WindowsXP-KB967048-v2-x86-ENU.exe' Run-Process $usbccidUpdate '/Nobackup /Overwriteoem /Quiet /Passive /Norestart' } else { DBG ('Update USBCCID driver on Windows 2003') $usbccidUpdate = Join-Path $global:installMediaVolume 'DRIVERS\USBCCID\WindowsServer2003-KB967048-x86-ENU.exe' Run-Process $usbccidUpdate '/Nobackup /Overwriteoem /Quiet /Passive /Norestart' } DBG ('Install Base smart card CSP update on Windows 5.x') $baseCSPUpdate = Join-Path $global:installMediaVolume 'DRIVERS\SC-Base-Provider-Windows-KB909520-v1.000-x86-ENU.exe' Run-Process $baseCSPUpdate '/Nobackup /Overwriteoem /Quiet /Passive /Norestart' } $gemaltoMiniDrvFolder = Join-Path $global:installMediaVolume 'DRIVERS\Gemalto IDPrime Minidriver' $gemaltoMiniDrvPath = Join-Path $gemaltoMiniDrvFolder 'Gemalto.MiniDriver.IDPrime.inf' #Run-Process (Join-Path $env:SystemRoot 'System32\rundll32.exe') ('setupapi,InstallHinfSection DefaultInstall 128 "{0}"' -f $gemaltoMiniDrvPath) $false $null $null $null $true $gemaltoMiniDrvFolder Run-Process (Join-Path $env:SystemRoot 'System32\rundll32.exe') ('advpack.dll,LaunchINFSectionEx "{0}",DefaultInstall,,4N' -f $gemaltoMiniDrvPath) $false $null $null $null $true $gemaltoMiniDrvFolder # Note: this method is not silent, it displays final confirmation dialog box #Run-Process (Join-Path $env:SystemRoot 'System32\InfDefaultInstall.exe') ('"{0}",1' -f $gemaltoMiniDrvPath) $false $null $null $null $true $gemaltoMiniDrvFolder DBG ('Should we also install the Gemalto Minidriver Manager: {0}' -f (Parse-BoolSafe $vmConfig.sc.gemaltoMgr)) if (Parse-BoolSafe $vmConfig.sc.gemaltoMgr) { $gemaltoMiniDrvManager = Join-Path $global:installMediaVolume 'DRIVERS\Gemalto Minidriver Manager\MiniDriver_Manager_v2301.msi' DBG ('Install Gemalto Minidriver Manager: {0} | exists = {1}' -f $gemaltoMiniDrvManager, (Test-Path $gemaltoMiniDrvManager)) DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $gemaltoMiniDrvManager) } if (Test-Path $gemaltoMiniDrvManager) { Run-Process msiexec ('-i "{0}" -qb' -f $gemaltoMiniDrvManager) } } DBG ('Install Monet+ ProId') if ($global:thisOSArchitecture -eq '64-bit') { DBG ('Install MonetPlus ProId on 64-bit platform') $proIdInstallPath = Join-Path $global:installMediaVolume 'DRIVERS\ProId\x64\Setup\setup_proid+_v2.0.7_x64.exe' Run-Process $proIdInstallPath '/L"1033" /V"-qb ADDLOCAL=Proidplus_CM_Files,Spravce_karty ADMINMODE=1 SSTART=1 SDESKTOP=0 REMOTEUNBLOCK=0"' } else { DBG ('Install MonetPlus ProId on 32-bit platform') $proIdInstallPath = Join-Path $global:installMediaVolume 'DRIVERS\ProId\x86\Setup\setup_proid+_v2.0.7_x86.exe' Run-Process $proIdInstallPath '/L"1033" /V"-qb ADDLOCAL=Proidplus_CM_Files,Spravce_karty ADMINMODE=1 SSTART=1 SDESKTOP=0 REMOTEUNBLOCK=0"' } ## ReDisable-SystemRestore ReDisable-Updates } #===================== DBG ('Do we require USB over Network client: {0}' -f (Parse-BoolSafe $vmConfig.sc.usbOverNetwork)) if (Parse-BoolSafe $vmConfig.sc.usbOverNetwork) { Install-USBoverNetwork 'client' $vmConfig.sc.usbServerConn (Parse-BoolSafe $vmConfig.sc.noAutoConnect) } #===================== DBG ('Install Network Monitor (netmon): {0}' -f (Parse-BoolSafe $vmConfig.install.netMon)) if (Parse-BoolSafe $vmConfig.install.netMon) { if ($global:thisOSArchitecture -eq '32-bit') { $arch = 'x86' } else { $arch = 'x64' } [string] $netmonProductId = '{8C5B5A11-CBF8-451B-B201-77FAB0D0B77D}' DBG ('First check if the Network Monitor (netmon) is not already installed: {0}' -f $netmonProductId) $netmonInstalled = Get-WmiQuerySingleObject '.' ('SELECT * FROM Win32_Product WHERE IdentifyingNumber = "{0}"' -f $netmonProductId) if (Is-Null $netmonInstalled) { DBG ('Install network monitor (netmon) for platform: {0} | {1}' -f $arch, $global:thisOSArchitecture) Run-Process 'msiexec' ('-i "{0}" ALLUSERS=1 -qb' -f (Join-Path $global:installMediaVolume ('NetworkMonitor\network-monitor-NM34_{0}\netmon.msi' -f $arch))) $nmpSearchPath = Join-Path $global:installMediaVolume ('NetworkMonitor\network-monitor-NM34_{0}' -f $arch) DBG ('Find all netmon parsers available in: {0}' -f $nmpSearchPath) $nmParsers = Get-ChildItem -Path $nmpSearchPath -Filter 'nmp-??-*.msi' | Select-Object -ExpandProperty FullName | Sort-Object DBG ('NetMon parsers found: {0}' -f (Get-CountSafe $nmParsers)) if ((Get-CountSafe $nmParsers) -gt 0) { $nmParsers | % { DBG ('Install NM parsers from MSI: {0}' -f $_) Run-Process 'msiexec' ('-i "{0}" ALLUSERS=1 -qb' -f $_) } } } # Note: the netmon preferences go into HKCU, so it would be too heavy a code to make it work #DBG ('Update netmon user defaults') #Run-Process 'REGEDIT' ('/S "{0}"' -f (Join-Path $global:rootDir 'User-Env\netmon-defaults.reg')) ## ReDisable-SystemRestore ReDisable-Updates } #===================== DBG ('Install Office: {0}' -f (Is-ValidString $vmConfig.install.office)) if (Is-ValidString $vmConfig.install.office) { $officeVersionTypes = Split-MultiValue $vmConfig.install.office $officeVersion = $officeVersionTypes[0] DBGIF ('Unsupported Office version: {0}' -f $officeVersion) { ($officeVersion -ne '2010') -and ($officeVersion -ne '2013') -and ($officeVersion -ne '2016') } if ($officeVersionTypes.Count -gt 1) { $officeArch = $officeVersionTypes[1] } else { if ($global:thisOSArchitecture -eq '32-bit') { $officeArch = '32bit' } else { $officeArch = '64bit' } } $officeArchShort = $officeArch.Substring(0,2) DBGIF $MyInvocation.MyCommand.Name { -not (($officeArch -eq '64bit') -or ($officeArch -eq '32bit')) } DBGIF $MyInvocation.MyCommand.Name { -not (($global:thisOSArchitecture -eq '64-bit') -or ($officeArch -eq '32bit')) } DBG ('Install Office for platform: {0} | {1} | {2} | {3}' -f $officeVersion, $officeArch, $officeArchShort, $global:thisOSArchitecture) $officeSetupPath = Join-Path $global:installMediaVolume ('Office\Office{0}_{1}\setup.exe' -f $officeVersion, $officeArch) $officeConfigFile = Get-DataFileApp ('office-config-{0}' -f ((Get-Date).ToString('s').Replace(':','-'))) $null '.xml' Replace-ArgumentsInFile ('{0}\Office\office-pro-plus-{1}-config.xml' -f $global:rootDir, $officeVersion) ('logPath${0}|bitEdition${1}' -f (Escape-ForMultiValue $global:outPath), $officeArchShort) $officeConfigFile ASCII DBG ('Going to install office: {0} | {1} | {2}' -f $officeVersion, $officeSetupPath, $officeConfigFile) if ($officeVersion -eq '2016') { Run-Process $officeSetupPath ('/configure "{0}"' -f $officeConfigFile) -adjustWorkDir $true Wait-Periodically -maxTrialCount 170 -sleepSec 7 -sleepMsg 'Waiting until office 2016 installer finishes' -scriptBlockWhichReturnsTrueToStop { # Note: it is always 32bit setup regardless of edition installed actually $officeSetupEXEs = Get-WmiQueryArray '.' ('SELECT * FROM Win32_Process WHERE Name = "setup32.exe" AND ExecutablePath LIKE "{1}\\Office\\%"' -f $officeArchShort, (Split-Path -Parent $officeSetupPath).Replace('\', '\\')) return ((Get-CountSafe $officeSetupEXEs) -eq 0) } } else { Run-Process $officeSetupPath ('/config "{0}"' -f $officeConfigFile) } # # [string] $officeUpdatesPath = Join-Path $global:installMediaVolume ('Office\Office{0}_{1}_updates' -f $officeVersion, $officeArch) DBG ('Check if any updates available: {0}' -f $officeUpdatesPath) if (Test-Path $officeUpdatesPath) { [string[]] $officeUpdates = Get-ChildItem $officeUpdatesPath | select -Expand FullName | ? { $_ -like '*.exe' } | sort DBG ('Found office updates: #{0}' -f (Get-CountSafe $officeUpdates)) if ((Get-CountSafe $officeUpdates) -gt 0) { foreach ($oneOfficeUpdate in $officeUpdates) { $oneOfficeUpdateLog = Get-DataFileApp ('office-update-{0}-{1}-{2}' -f $officeVersion, $officeArch, ([System.IO.Path]::GetFileName($oneOfficeUpdate))) $null '.log' DBG ('Going to install one office update: {0} | log = {1}' -f $oneOfficeUpdate, $oneOfficeUpdateLog) Run-Process $oneOfficeUpdate ('/passive /norestart "/log:{0}"' -f $oneOfficeUpdateLog) } } } ## ReDisable-SystemRestore ReDisable-Updates } #===================== DBG ('Install SharePoint Designer (SPD): {0}' -f (Is-ValidString $vmConfig.install.spd)) if (Is-ValidString $vmConfig.install.spd) { foreach ($oneSPD in (Split-MultiValue $vmConfig.install.spd)) { DBG ('One SharePoint Designer instance to be installed: {0}' -f $oneSPD) $officeArch = Get-ValueFlags $oneSPD $officeVersion = Strip-ValueFlags $oneSPD <# if ($officeVersionTypes.Count -gt 1) { $officeArch = $officeVersionTypes[1] } else { if ($global:thisOSArchitecture -eq '32-bit') { $officeArch = '32bit' } else { $officeArch = '64bit' } } #> DBGIF $MyInvocation.MyCommand.Name { -not (($officeArch -eq '64bit') -or ($officeArch -eq '32bit')) } DBGIF $MyInvocation.MyCommand.Name { -not (($global:thisOSArchitecture -eq '64-bit') -or ($officeArch -eq '32bit')) } DBG ('Install SharePoint Designer for platform: {0} | {1} | {2}' -f $officeVersion, $officeArch, $global:thisOSArchitecture) $officeSetupPath = Join-Path $global:installMediaVolume ('SharePointDesigner\SharePointDesigner{0}_{1}\setup.exe' -f $officeVersion, $officeArch) $officeConfigFile = Get-DataFileApp ('office-config-{0}' -f ((Get-Date).ToString('s').Replace(':','-'))) $null '.xml' Replace-ArgumentsInFile "$global:rootDir\Office\sharepoint-designer-config.xml" ('logPath${0}' -f (Escape-ForMultiValue $global:outPath)) $officeConfigFile ASCII DBG ('Going to install SharePoint Designer: {0} | {1}' -f $officeSetupPath, $officeConfigFile) Run-Process $officeSetupPath ('/config "{0}"' -f $officeConfigFile) ## ReDisable-SystemRestore ReDisable-Updates } } #===================== DBG ('Install Office365 PowerShell aka Windows Azure Active Directory Module for Powershell aka AAD PowerShell aka Azure AD Module for PowerShell: {0}' -f (Parse-BoolSafe $vmConfig.install.office365PowerShell)) if (Parse-BoolSafe $vmConfig.install.office365PowerShell) { if ($global:thisOSArchitecture -eq '32-bit') { Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-service-assistant-v7.250.4556.0-msoidcli_32.msi')) DBGIF ('The 32bit version of Azure AD Powershell is not supported anymore') { $true } #Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-ad-powershell-v1.0.0-x32-AdministrationConfig-en.msi')) } else { Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-service-assistant-v7.250.4556.0-msoidcli_64.msi')) Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-ad-powershell-v1.0.0-x64-AdministrationConfig-en.msi')) } DBG ('Check if we do have any newer version of Azure PowerShell available') DBGSTART $newestAzurePowerShell = Get-ChildItem (Join-Path $global:installMediaVolume 'Azure') | ? { $_.Name -like 'azure-powershell.*.msi' } | select -Expand FullName | sort | select -Last 1 DBGER $MyInvocation.MyCommand.Name $error DBGEND if ((Is-ValidString $newestAzurePowerShell) -and ($global:thisOSArchitecture -eq '64-bit')) { DBG ('Installing the newest version of Azure PowerShell module: {0}' -f $newestAzurePowerShell) Run-Process msiexec ('-i "{0}" -qb -norestart' -f $newestAzurePowerShell) } ## ReDisable-SystemRestore ReDisable-Updates } #===================== DBG ('Install Visual Studio Express: {0}' -f (Is-ValidString $vmConfig.install.vsExpress)) if (Is-ValidString $vmConfig.install.vsExpress) { DBG ('VS Express installation requirements: {0}' -f $vmConfig.install.vsExpress) foreach ($oneVSExpress in (Split-MultiValue $vmConfig.install.vsExpress)) { DBG ('One VS Express tool: {0}' -f $oneVSExpress) $vsExpressVersion = Strip-ValueFlags $oneVSExpress $vsExpressType = Get-ValueFlags $oneVSExpress DBG ('Going to install VS Express: {0} | {1}' -f $vsExpressVersion, $vsExpressType) Run-Process (Join-Path $global:installMediaVolume ('VSExpress{0}\{1}\vns_full.exe' -f $vsExpressVersion, $vsExpressType)) '/passive /norestart' ## ReDisable-SystemRestore ReDisable-Updates } } # Note: this does not disable the "builtin admin" actually # we just disble the "original admin" that the buildup started with # Although it will most probably be the builtin admin account, it may not # be 100% guaranteed DBG ('Should disable original builtin admin: {0}' -f (Parse-BoolSafe($vmConfig.domain.originalAdmin.disable))) $originalAdmin = $global:phaseCfg.sevecekBuildup.originalAdmin.login if (Parse-BoolSafe($vmConfig.domain.originalAdmin.disable)) { DBGIF ('Disabling current autologin identity. This may not autologon anymore' -f $originalAdmin) { $originalAdmin -eq $global:phaseCfg.sevecekBuildup.login.autoLogin } Disable-LocalUser $originalAdmin } DBG ('Should auto-rename original builtin admin: {0}' -f (Parse-BoolSafe($vmConfig.domain.originalAdmin.autoName))) if (Parse-BoolSafe $vmConfig.domain.originalAdmin.autoName) { if (($global:thisOSRole -eq 'BDC') -or ($global:thisOSRole -eq 'PDC')) { [string] $newNamePrefix = $global:thisComputerDomainNetBIOS } else { [string] $newNamePrefix = $global:thisComputerNetBIOS } #$newAdminName = '{0}-{1}' -f ($newNamePrefix.SubString(0, [Math]::Min([Math]::Max((20 - $originalAdmin.Length - 1), 0), $newNamePrefix.Length))), $originalAdmin $newAdminName = '{0}-admin' -f ($newNamePrefix.ToLower().SubString(0, [Math]::Min(14, $newNamePrefix.Length))) Rename-LocalObj 'user' $originalAdmin $newAdminName if (($originalAdmin -eq $global:phaseCfg.sevecekBuildup.login.autoLogin) -and (Is-LocalDomain $global:phaseCfg.sevecekBuildup.login.domain)) { $global:phaseCfg.sevecekBuildup.login.autoLogin = [string] $newAdminName } $global:phaseCfg.sevecekBuildup.originalAdmin.login = $newAdminName if (($global:thisOSRole -eq 'BDC') -or ($global:thisOSRole -eq 'PDC')) { [System.Collections.ArrayList] $deList = @() $foundDE = Get-DEbyDNorSAMorUPN $newAdminName 'user' (Get-NCName $global:thisComputerDomain 'nCName' 'dNSRoot') ([ref] $deList) if (Is-ValidString (GDES $foundDE userPrincipalName)) { DBGSTART $foundDE.Put('userPrincipalName', ('{0}@{1}' -f $newAdminName, $global:thisComputerDomain)) $adsiRs = $memberDE.SetInfo() DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ("ADSI Result: {0}" -f ($adsiRs | Out-String)) } Rename-Object (GDES $foundDE distinguishedName) ('CN={0}' -f $newAdminName) Dispose-List ([ref] $deList) } } # SIG # Begin signature block # MIIYMAYJKoZIhvcNAQcCoIIYITCCGB0CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCg2QTf/KTUOKdg # KM9NtQdt6QwZ5rGOiexd3PqfYxgetaCCE0cwggYEMIID7KADAgECAgoqHIRwAAEA # AAB/MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAkNaMRcwFQYDVQQIEw5DemVj # aCBSZXB1YmxpYzENMAsGA1UEBxMEQnJubzEQMA4GA1UEChMHU2V2ZWNlazEjMCEG # A1UEAxMaU2V2ZWNlayBFbnRlcnByaXNlIFJvb3QgQ0EwHhcNMTkwNjExMTkyMzMy # WhcNMjQwNjA5MTkyMzMyWjCBjzELMAkGA1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNo # IFJlcHVibGljMQ0wCwYDVQQHEwRCcm5vMRwwGgYDVQQKExNJbmcuIE9uZHJlaiBT # ZXZlY2VrMRcwFQYDVQQDEw5PbmRyZWogU2V2ZWNlazEhMB8GCSqGSIb3DQEJARYS # b25kcmVqQHNldmVjZWsuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC # AQEAnkjWNkK4FfUUN8iAN91ry+wsSn8cFKJbMnROAqTrx8t3H315p2/bUG2DosCF # Odu0WcaTOLdm5obhT+/3O7BqpdcnlWKlSEz4AL9zQeCbe4++NObBVPBbPE16j9C4 # xELoXW/Ti86C2PEkN5azGUvxGxzQQ45g32OsEI+Bh05qHMkk3oQ6L8O0Fpd5W4e+ # L4HuKS3JOikNhhryTNPD9grF/0wXTzn94TrL1GohuaCPh8g9HOtMoDCd+ExnqV8q # 4k60D37BOK1I81hYFIBn8MvCsjMRC5TK87MtI7aUUIeve5kopc8ZpxNti3F/+Puh # 4UUxL3nKjfAM6HE0b7FqkfkRpwIDAQABo4IBgjCCAX4wEwYDVR0lBAwwCgYIKwYB # BQUHAwMwDgYDVR0PAQH/BAQDAgbAMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUH # AwMwHQYDVR0OBBYEFOKbNkkiAht2GxCISJMJxLg4gOC9MB0GA1UdEQQWMBSBEm9u # ZHJlakBzZXZlY2VrLmNvbTAfBgNVHSMEGDAWgBQNnMgyfdUi8l9UfithS4FQ88Vs # wDBSBgNVHR8ESzBJMEegRaBDhkFodHRwOi8vcGtpLnNldmVjZWsuY29tL0NBL1Nl # dmVjZWslMjBFbnRlcnByaXNlJTIwUm9vdCUyMENBKDEpLmNybDCBhgYIKwYBBQUH # AQEEejB4ME0GCCsGAQUFBzAChkFodHRwOi8vcGtpLnNldmVjZWsuY29tL0NBL1Nl # dmVjZWslMjBFbnRlcnByaXNlJTIwUm9vdCUyMENBKDEpLmNydDAnBggrBgEFBQcw # AYYbaHR0cDovL3BraS5zZXZlY2VrLmNvbS9vY3NwMA0GCSqGSIb3DQEBCwUAA4IC # AQCfr6XDtt/O8OBr+X5l49UBLaJrjUXHkAHofdC7p7BLCXIs4GYIti1lf6pas5yB # Q428aKITITq/vEHUTyiiyKtzVkafILWXXKPxy+zmmuw9odB3Hea4ECNpcaG8UNtz # vMm1Dr0ZrkENhcv6I3tNhRr2AOE9AKOfnVEullFD/mZqfmaNkhpnl31jk7OMSUQc # oY8qD6BDQP9371C10gJOmp57sHfPa4Vn5E4aNzn8+o9C9HI7nNagZF5BamKOFdR2 # ui7K3krMbTuDHo+ZcA9nHnzZqiVKpEBFu8lGv9Mf+GDb9yxz6EjV3xS2RcnywX2v # z0VUt2NGno8LudrnWpgrRy4Sl7x6FwVVKtS/o7zFSIiHgntIKFv8urSKSTukCLFK # Y9fBIDDlWFV1ZV1DNpNWxnexWIRv2AH7YlzKQCA4Rysn01hVeBGsWFkCr9J33LmV # enQYpk9eoYMPRwAYg48r65wOOOzLvmyLSGllH88BMvmTQ9myXqwp6NDH1psljXTl # PUbpf7w6IZwsY0dhGhP9iyqbcrGdK0Bnf8Za6Qdj3iXtwd1VgpatFZrxOM5KawCL # pkYl1ABupbzNpWzmC+nfymqwbYiCogPt1vHOyF4EJ73ExVDCqXkpiNvFRqmu1eaZ # IOdbPCdl00a9rk52NKqo/BUsw16TKsDEYTA/7ACbEsnERzCCBmowggVSoAMCAQIC # EAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCVVMx # FTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv # bTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMB4XDTE0MTAyMjAw # MDAwMFoXDTI0MTAyMjAwMDAwMFowRzELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp # Z2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2VydCBUaW1lc3RhbXAgUmVzcG9uZGVyMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2Rd/Hyz4II14OD2xirmSXU7 # zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1VpjWwJJUNmDzm9m7t3LhelfpfnUh3SIRDsZ # yeX1kZ/GFDmsJOqoSyyRicxeKPRktlC39RKzc5YKZ6O+YZ+u8/0SeHUOplsU/UUj # joZEVX0YhgWMVYd5SEb3yg6Np95OX+Koti1ZAmGIYXIYaLm4fO7m5zQvMXeBMB+7 # NgGN7yfj95rwTDFkjePr+hmHqH7P7IwMNlt6wXq4eMfJBi5GEMiN6ARg27xzdPpO # 2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT88lhzNAIzGvsYkKRrALA76TwiRGPdwID # AQABo4IDNTCCAzEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0l # AQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCGSAGG/WwH # ATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw # ggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgA # aQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQA # ZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcA # aQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwA # eQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkA # YwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEA # cgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIA # eQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaA # FBUAEisTmLKZB+0e36K+Vw0rZwLNMB0GA1UdDgQWBBRhWk0ktkkynUoqeRqDS/Qe # icHKfTB9BgNVHR8EdjB0MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v # RGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDA4oDagNIYyaHR0cDovL2NybDQuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwdwYIKwYBBQUHAQEE # azBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYB # BQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy # ZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCdJX4bM02yJoFcm4bOIyAP # gIfliP//sdRqLDHtOhcZcRfNqRu8WhY5AJ3jbITkWkD73gYBjDf6m7GdJH7+IKRX # rVu3mrBgJuppVyFdNC8fcbCDlBkFazWQEKB7l8f2P+fiEUGmvWLZ8Cc9OB0obzpS # CfDscGLTYkuw4HOmksDTjjHYL+NtFxMG7uQDthSr849Dp3GdId0UyhVdkkHa+Q+B # 0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW8LsKqxzbXEgnZsijiwoc5ZXarsQuWaBh # 3drzbaJh6YoLbewSGL33VVRAA5Ira8JRwgpIr7DUbuD0FAo6G+OPPcqvao173NhE # MIIGzTCCBbWgAwIBAgIQBv35A5YDreoACus/J7u6GzANBgkqhkiG9w0BAQUFADBl # MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 # d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv # b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMjExMTEwMDAwMDAwWjBiMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDogi2Z+crCQpWlgHNAcNKeVlRcqcTS # QQaPyTP8TUWRXIGf7Syc+BZZ3561JBXCmLm0d0ncicQK2q/LXmvtrbBxMevPOkAM # Rk2T7It6NggDqww0/hhJgv7HxzFIgHweog+SDlDJxofrNj/YMMP/pvf7os1vcyP+ # rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds97bFBo+0/vtuVSMTuHrPyvAwrmdDGXRJC # geGDboJzPyZLFJCuWWYKxI2+0s4Grq2Eb0iEm09AufFM8q+Y+/bOQF1c9qjxL6/s # iSLyaxhlscFzrdfx2M8eCnRcQrhofrfVdwonVnwPYqQ/MhRglf0HBKIJAgMBAAGj # ggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYwOwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsG # AQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMIIB0gYDVR0gBIIB # yTCCAcUwggG0BgpghkgBhv1sAAEEMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3 # dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUF # BwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUA # cgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMA # YwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQA # IABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAA # UABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkA # bQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4A # YwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYA # ZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwEgYDVR0TAQH/BAgwBgEB/wIBADB5 # BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 # LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNy # bDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl # ZElEUm9vdENBLmNybDAdBgNVHQ4EFgQUFQASKxOYspkH7R7for5XDStnAs0wHwYD # VR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEFBQADggEB # AEZQPsm3KCSnOB22WymvUs9S6TFHq1Zce9UNC0Gz7+x1H3Q48rJcYaKclcNQ5IK5 # I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2hHfMJKXzBBlVqefj56tizfuLLZDCwNK1 # lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmnxPXOHXh2lCVz5Cqrz5x2S+1fwksW5Etw # TACJHvzFebxMElf+X+EevAJdqP77BzhPDcZdkbkPZ0XN1oPt55INjbFpjE/7WeAj # D9KqrgB87pxCDs+R1ye3Fu4Pw718CqDuLAhVhSK46xgaTfwqIa1JMYNHlXdx3LEb # S0scEJx3FMGdTy9alQgpECYxggQ/MIIEOwIBATB6MGwxCzAJBgNVBAYTAkNaMRcw # FQYDVQQIEw5DemVjaCBSZXB1YmxpYzENMAsGA1UEBxMEQnJubzEQMA4GA1UEChMH # U2V2ZWNlazEjMCEGA1UEAxMaU2V2ZWNlayBFbnRlcnByaXNlIFJvb3QgQ0ECCioc # hHAAAQAAAH8wDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA # oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w # DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg9Trv9eoa346MG3wdAHyzz286 # TRhyYY5/Zv9vqI+/DFAwDQYJKoZIhvcNAQEBBQAEggEAH2jXR3dmjiSD0aMLMlCU # Rys80nEQKhHAk0pRV5G/P9mJhqU2gGfV2xYkTEvDD1SvpQduueMMqTwp0Zw0ngMw # ZHYcdOSUeqskiA7VsZQFtBs7wm+TOAbW6va5f+Y7/XhgRUNNSPikcqy8/rjHIOzp # KcnNMn4TLClWpYAfeQIBfDFYUPX8akvIRwxX5U0oupEqLBBcq+zSC/9KH2ZICzQu # M11nLwsjvnZUTnuzi8TEsbYD9z65bWVrj0s3WXykcJMjuILh8+0aXRg6ukjd0B8i # Y/mYTMxIckkD1MdYSO+SxAsNrfXwssOE/Cly9XARULwWF1unQNWDBkz99EJCJqaS # NaGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr # 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc # BgkqhkiG9w0BCQUxDxcNMTkwNjEzMDM1MzAwWjAjBgkqhkiG9w0BCQQxFgQUvPnF # 3omzUbxWoeUKIBDKenRWcSMwDQYJKoZIhvcNAQEBBQAEggEAhXDHggbQkE9dmP2K # ZErbMRcPTC5padwLR/YvbNUr9CZy6Z7GlBXmN0NWYfnSSQsIPJFEytcz32A/ojyc # nfxB/W6kE7WdO02v1xILmDE8PvNuI/8Y5zwNTlmr9kBiuAz44eopsf6MSkY05Sqk # jVAO2C75jX4mcSJ/VT2IyF4u/Tu8UVyciKVQpd51xFKUGStqJKYY84dz+idksOhc # ObT8LO6OsBYnA2Q6Uj5xUIPPiI7Oi8ryqkH2YTJnFVaMgD0gzUMYMVVFJv5yPj64 # eyEHipsHxA0GpEtLjPdl9WZImZuB0PuC/4pjGOjnrOvUokRLg8waLNatubwcYqd6 # hnDtyQ== # SIG # End signature block