12 Commits
Author SHA1 Message Date
djpbessems b4f62b465d Update packer windows-update plugin
continuous-integration/drone/push Build is passing
2021-10-25 13:32:57 +02:00
djpbessems 060ac37dc0 Update VMware Tools 2021-10-25 13:12:00 +02:00
djpbessems b75694479a Migrate to HCL;Add 'packer init' step;Add variable definitions;Add debugging information
continuous-integration/drone/push Build is passing
2021-06-05 19:34:31 +02:00
djpbessems b304a17ee1 Change to Windows 10 Enterprise #2
continuous-integration/drone/push Build is passing
2021-04-11 18:23:23 +02:00
djpbessems d155db26e7 Change to Windows 10 Enterprise
continuous-integration/drone/push Build is failing
2021-04-11 18:13:00 +02:00
djpbessems b2f607e2b5 Enable builtin administrator (override default sysprep behaviour)
continuous-integration/drone/push Build is passing
2021-04-11 11:46:22 +02:00
djpbessems 465088b455 Fix staging directory
continuous-integration/drone/push Build is passing
2021-04-10 16:47:47 +02:00
djpbessems 3db5e7dbf7 Update hypervisor FQDN
continuous-integration/drone/push Build is failing
2021-04-10 15:35:15 +02:00
djpbessems f84e065e15 Add pull policy
continuous-integration/drone/push Build is failing
2021-04-10 15:34:04 +02:00
djpbessems 00557e50f9 Replay sidestream changes
continuous-integration/drone/push Build is failing
2021-04-10 15:32:31 +02:00
djpbessems e455419986 Update badge in README 2021-01-27 21:32:42 +01:00
djpbessems 08b63f30d6 Delete commit history (containing proprietary code)
continuous-integration/drone/push Build is passing
2021-01-24 09:26:53 +01:00
86 changed files with 723 additions and 2302 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

+45 -6
View File
@@ -2,6 +2,14 @@ kind: pipeline
type: kubernetes
name: 'Packer Build'
volumes:
- name: output
claim:
name: flexvolsmb-drone-output
- name: scratch
claim:
name: flexvolsmb-drone-scratch
steps:
- name: Debugging information
image: bv11-cr01.bessems.eu/library/packer-extended
@@ -10,10 +18,15 @@ steps:
- packer --version
- pwsh --version
- ovftool --version
- name: Active Directory Domain Services
- name: Windows 10
image: bv11-cr01.bessems.eu/library/packer-extended
pull: always
commands:
- sed -i -e "s/<<img-productkey>>/$${PRODUCTKEY}/" packer/preseed/Windows10/Autounattend.xml
- |
sed -i -e "s/<<img-password>>/$${WINRM_PASSWORD}/g" \
packer/preseed/Windows10/Autounattend.xml \
packer/preseed/Windows10/Sysprep_Unattend.xml
- |
yamllint -d "{extends: relaxed, rules: {line-length: disable}}" scripts
- |
@@ -22,13 +35,19 @@ steps:
- |
packer validate \
-var vm_name=$DRONE_BUILD_NUMBER-${DRONE_COMMIT_SHA:0:10} \
-var vm_guestos=win10 \
-var repo_username=$${REPO_USERNAME} \
-var repo_password=$${REPO_PASSWORD} \
-var vsphere_password=$${VSPHERE_PASSWORD} \
-var winrm_password=$${WINRM_PASSWORD} \
./packer
- |
packer build \
-on-error=cleanup -timestamp-ui \
-on-error=cleanup \
-var vm_name=$DRONE_BUILD_NUMBER-${DRONE_COMMIT_SHA:0:10} \
-var vm_guestos=win10 \
-var repo_username=$${REPO_USERNAME} \
-var repo_password=$${REPO_PASSWORD} \
-var vsphere_password=$${VSPHERE_PASSWORD} \
-var winrm_password=$${WINRM_PASSWORD} \
./packer
@@ -37,12 +56,32 @@ steps:
from_secret: vsphere_password
WINRM_PASSWORD:
from_secret: winrm_password
REPO_USERNAME:
from_secret: repo_username
REPO_PASSWORD:
from_secret: repo_password
PRODUCTKEY:
from_secret: prodkey_win10
# PACKER_LOG: 1
volumes:
- name: output
path: /output
- name: Remove temporary resources
image: bv11-cr01.bessems.eu/library/packer-extended
commands:
- |
pwsh -file scripts/Remove-Resources.ps1 \
-VMName $DRONE_BUILD_NUMBER-${DRONE_COMMIT_SHA:0:10} \
-VSphereFQDN 'bv11-vc.bessems.lan' \
-VSphereUsername 'administrator@vsphere.local' \
-VSpherePassword $${VSPHERE_PASSWORD}
environment:
VSPHERE_PASSWORD:
from_secret: vsphere_password
volumes:
- name: output
claim:
name: flexvolsmb-drone-output
- name: scratch
path: /scratch
when:
status:
- success
- failure
+1 -108
View File
@@ -1,108 +1 @@
# Packer.Images [![Build Status](https://ci.spamasaurus.com/api/badges/djpbessems/Packer.Images/status.svg?ref=refs/heads/ADDS)](https://ci.spamasaurus.com/djpbessems/Packer.Images)
This OVA appliance allows deploying an Active Directory Domain Controller fully automated:
The included `.ovf` file has the following XML contents (simplified for clarity) to facilitate the different `DeploymentOption`s:
```xml
<Envelope [...]>
[...]
<DeploymentOptionSection>
<Info>Deployment Type</Info>
<Configuration ovf:id="primary">
<Label>Primary (redundant deployment)</Label>
<Description>Initial Domain Controller with 'PDC Emulator'-role</Description>
</Configuration>
<Configuration ovf:id="secondary">
<Label>Secondary (redundant deployment)</Label>
<Description>Additional Domain Controller</Description>
</Configuration>
<Configuration ovf:id="standalone">
<Label>Stand-alone (non-redundant deployment)</Label>
<Description>Single Domain Controller</Description>
</Configuration>
</DeploymentOptionSection>
<VirtualSystem ovf:id="[...]">
[...]
<ProductSection>
[...]
<Category>1) Operating System</Category>
<Property ovf:configuration="primary secondary standalone" ovf:key="guestinfo.hostname" [...]>
<Label>Hostname*</Label>
</Property>
[...]
<Category>2) Networking</Category>
<Property ovf:configuration="secondary" ovf:key="guestinfo.dnsserver" [...]>
<Label>DNS server*</Label>
</Property>
[...]
<Category>3) Active Directory Domain Services</Category>
<Property ovf:configuration="primary standalone" ovf:key="addsconfig.ntpserver" [...]>
<Label>NTP Server*</Label>
[...]
</Property>
</ProductSection>
</VirtualSystem>
</Envelope>
```
When **provisioning** the appliance through the vCenter 'Deploy OVF template...' wizard, or through vApp-compatible *Infrastructure as code* tooling (e.g. HashiCorp Terraform), it is possible to provide all relevant configuration through vApp properties.
<table>
<tr>
<td><em>vSphere 'Deploy OVF template...' wizard</em></td> <td> <a href="https://registry.terraform.io/providers/hashicorp/vsphere/latest/docs/resources/virtual_machine#deploying-vm-from-an-ovfova-template">HashiCorp Terraform vSphere provider</a> </td>
</tr>
<tr>
<td><img src=".assets/vAppConfigurations-ADDS-example.png" alt="vApp properties" width="400" /><br/><img src=".assets/vAppProperties-ADDS-example.png" alt="vApp properties" width="400" /></td>
<td>
```hcl
vapp {
properties = {
# "deployment.type = "primary"
"guestinfo.hostname" = "DC01"
"guestinfo.ipaddress" = "10.0.0.21"
"guestinfo.prefixlength" = "24"
# "guestinfo.dnsserver" = "0.0.0.0"
"guestinfo.gateway" = "10.0.0.1"
"addsconfig.domainname" = "contoso.com"
"addsconfig.netbiosname" = "CONTOSO"
"addsconfig.administratorpw" = var.adds_adminpassword
"addsconfig.safemodepw" = var.adds_safemodepassword
# "addsconfig.ntpserver" = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org"
"vault.api" = "https://vault.example.org/v1"
"vault.token" = var.vault_token
"vault.pwpolicy" = "complex"
"vault.secret" = "contoso-project42"
# "dhcpconfig.startip" = "10.0.0.50"
# "dhcpconfig.endip" = "10.0.0.250"
# "dhcpconfig.subnetmask" = "255.255.255.0"
# "dhcpconfig.gateway" = "10.0.0.1"
# "dhcpconfig.leaseduration" = "01:00:00.00"
}
}
```
</td>
</tr>
</table>
On first boot, the appliance will start **configuring** itself without any further user-input, by performing the following steps:
- Change hostname
- Configure network
- Set password for local administrator
- Promote to Domain Controller
- Iterate through all payload scripts:
- Create Active Directory Organizational Units
- Create Active Directory security groups
- Create Active Directory user accounts
- Set up Delegation of Control
- Configure Active Directory Group Policy Objects with Windows Firewall settings
- Configure DHCP (scopes, options and Failover relationship)
- Create DNS records
- Define Active Directory Group Policy WMI Filters
- Define and link Active Directory Group Policy Objects and Preferences
- Set Active Directory Default domain Password policy
# Packer.Images [![Build Status](https://ci.spamasaurus.com/api/badges/djpbessems/Packer.Images/status.svg?ref=refs/heads/Windows10)](https://ci.spamasaurus.com/djpbessems/Packer.Images)
@@ -0,0 +1,16 @@
script = <<-EOH
$nic = get-netadapter
Get-NetAdapterBinding –InterfaceAlias $nic.name –ComponentID ms_tcpip6
EOH
control "ipv6" do
title 'Disabled network protocol IPv6'
desc '
This test assures that IPv6 is disabled
'
describe powershell(script) do
its('stdout') { should match 'False' }
end
end
@@ -0,0 +1,29 @@
script = <<-EOH
# Initialize variable to empty array
$NonCompliantServices = @()
# Specify relevant services
$Services = @(
"wuauserv",
"W3SVC",
"XboxGipSvc",
"XblGameSave"
)
# Enumerate all services
$NonCompliantServices += Get-Service $Services -ErrorAction 'SilentlyContinue' | Where-Object {$_.StartType -ne 'Disabled'}
# Output; 'True' or list of noncompliant services
Write-Output ($True, $NonCompliantServices)[!($NonCompliantServices.Count -eq 0)]
EOH
control "disabled_services" do
title 'Disabled services'
desc '
This test assures that all unneeded services are set to "disabled".
'
describe powershell(script) do
its('stdout') { should match 'True' }
end
end
@@ -0,0 +1,29 @@
script = <<-EOH
# Initialize variable to empty array
$LogicalDisks = @()
# Enumerate all logicaldisks
# DriveType:
# Unknown (0)
# No Root Directory (1)
# Removable Disk (2)
# Local Disk (3)
# Network Drive (4)
# Compact Disc (5)
# RAM Disk (6)
$LogicalDisks += Get-WmiObject -Class 'win32_logicaldisk' -Filter 'DriveType=3'
# Filter/Quantify
($LogicalDisks.Count -eq 1) -and (($LogicalDisks | Where-Object {$_.DeviceID -ne 'C:'}).Count -eq 0)
EOH
control "single_disk" do
title 'Single Disk'
desc '
This test assures that only a single disk (C:) is available
'
describe powershell(script) do
its('stdout') { should match 'True' }
end
end
@@ -0,0 +1,54 @@
control "software_installed-7zip" do
title 'Included Default Applications: 7-Zip'
desc '
This test assures that the software application "7-Zip" is installed.
'
describe chocolatey_package('7zip.install') do
it { should be_installed }
end
end
# control "software_installed-dotnetfx" do
# title 'Included Default Applications: .NET'
# desc '
# This test assures that the software application ".NET" is installed.
# '
# describe chocolatey_package('dotnetfx') do
# it { should be_installed }
# end
# end
# control "software_installed-foxitreader" do
# title 'Included Default Applications: Foxit Reader'
# desc '
# This test assures that the software application "Foxit Reader" is installed.
# '
# describe chocolatey_package('foxitreader') do
# it { should be_installed }
# end
# end
# control "software_installed-notepadplusplus" do
# title 'Included Default Applications: Notepad++'
# desc '
# This test assures that the software application "Notepad++" is installed.
# '
# describe chocolatey_package('notepadplusplus') do
# it { should be_installed }
# end
# end
# control "software_installed-putty" do
# title 'Included Default Applications: Putty'
# desc '
# This test assures that the software application "PuTTy" is installed.
# '
# describe chocolatey_package('putty') do
# it { should be_installed }
# end
# end
@@ -0,0 +1,10 @@
---
name: Windows 10 IoT Enterprise
title: Windows 10 IoT Enterprise InSpec Tests
summary: Unit test for Windows 10 IoT Enterprise
version: 1.0.0
maintainer: https://code.spamasaurus.com/djpbessems
copyright: https://code.spamasaurus.com/djpbessems
license: Proprietary
supports:
- platform-family: windows
-90
View File
@@ -1,90 +0,0 @@
packer {
required_plugins {
windows-update = {
version = ">= 0.12.0"
source = "github.com/rgl/windows-update"
}
}
}
source "vsphere-clone" "adds" {
vcenter_server = var.vcenter_server
username = var.vsphere_username
password = var.vsphere_password
insecure_connection = "true"
vm_name = "adds-${var.vm_name}"
datacenter = var.vsphere_datacenter
host = var.vsphere_host
folder = var.vsphere_folder
datastore = var.vsphere_datastore
template = "Windows-Server-2019-LTSC"
boot_order = "disk,cdrom"
boot_command = [""]
boot_wait = "2m30s"
communicator = "winrm"
winrm_password = var.winrm_password
winrm_timeout = "10m"
winrm_username = "administrator"
RAM = 8192
CPUs = 2
floppy_files = [
"packer/preseed/ADDS/Sysprep_Unattend.xml"
]
shutdown_command = "C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /unattend:A:\\Sysprep_Unattend.xml"
shutdown_timeout = "1h"
export {
images = false
}
}
build {
sources = ["source.vsphere-clone.adds"]
provisioner "powershell" {
inline = [
"New-Item -Path 'C:\\Payload\\Scripts' -ItemType 'Directory' -Force:$True -Confirm:$False"
]
}
provisioner "file" {
destination = "C:\\Payload\\"
source = "scripts/ADDS/payload/"
}
provisioner "powershell" {
scripts = [
"scripts/ADDS/Install-Prerequisites.ps1",
"scripts/ADDS/Register-ScheduledTask.ps1"
]
}
post-processor "shell-local" {
inline = [
"pwsh -command \"& scripts/Update-OvfConfiguration.ps1 \\",
" -OVFFile './output-adds/adds-${var.vm_name}.ovf' \\",
" -Parameter @{'appliance.name'='ADDS';'appliance.version'='${var.vm_name}'}\"",
"pwsh -file scripts/Update-Manifest.ps1 \\",
" -ManifestFileName './output-adds/adds-${var.vm_name}.mf'",
"ovftool --acceptAllEulas --allowExtraConfig --overwrite \\",
" './output-adds/adds-${var.vm_name}.ovf' \\",
" /output/ADDS-appliance.ova"
]
}
post-processor "shell-local" {
inline = [
"pwsh -file scripts/Remove-Resources.ps1 \\",
" -VMName 'adds-${var.vm_name}' \\",
" -VSphereFQDN '${var.vcenter_server}' \\",
" -VSphereUsername '${var.vsphere_username}' \\",
" -VSpherePassword '${var.vsphere_password}'"
]
}
}
+159
View File
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<servicing/>
<settings pass="windowsPE">
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<DiskConfiguration>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Extend>false</Extend>
<Format>NTFS</Format>
<Letter>C</Letter>
<Order>1</Order>
<PartitionID>1</PartitionID>
<Label>Windows 10</Label>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
<WillShowUI>OnError</WillShowUI>
</DiskConfiguration>
<UserData>
<AcceptEula>true</AcceptEula>
<!-- <FullName>Spamasaurus Rex</FullName>
<Organization>Spamasaurus Rex</Organization> -->
<ProductKey>
<Key><<img-productkey>></Key>
<WillShowUI>Never</WillShowUI>
</ProductKey>
</UserData>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>1</PartitionID>
</InstallTo>
<WillShowUI>OnError</WillShowUI>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>3</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>en-US</UserLocale>
</component>
</settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserAccounts>
<AdministratorPassword>
<Value><<img-password>></Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Home</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
</OOBE>
<AutoLogon>
<Password>
<Value><<img-password>></Value>
<PlainText>true</PlainText>
</Password>
<Username>administrator</Username>
<Enabled>true</Enabled>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
<Description>Set execution policy 64bit</Description>
<Order>1</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine>
<Description>Set execution policy 32bit</Description>
<Order>2</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c reg add "HKLM\System\CurrentControlSet\Control\Network\NewNetworkWindowOff"</CommandLine>
<Description>Disable new network prompt</Description>
<Order>3</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\Set-NetworkProfile.ps1</CommandLine>
<Description>Set network profile to private</Description>
<Order>4</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\Disable-WinRM.ps1</CommandLine>
<Description>Disable WinRM</Description>
<Order>5</Order>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c a:\Install-VMwareTools.cmd</CommandLine>
<Order>13</Order>
<Description>Install VMware Tools</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\Enable-WinRM.ps1</CommandLine>
<Description>Enable WinRM</Description>
<Order>99</Order>
</SynchronousCommand>
</FirstLogonCommands>
<ShowWindowsLive>false</ShowWindowsLive>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<OEMInformation>
<HelpCustomized>false</HelpCustomized>
</OEMInformation>
<!-- Rename computer here. -->
<ComputerName>packer-template</ComputerName>
<TimeZone>W. Europe Standard Time</TimeZone>
<RegisteredOwner/>
</component>
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SkipAutoActivation>true</SkipAutoActivation>
</component>
</settings>
</unattend>
@@ -10,6 +10,12 @@
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OOBE>
<HideEULAPage>true</HideEULAPage>
@@ -22,6 +28,15 @@
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
</OOBE>
<TimeZone>UTC</TimeZone>
<UserAccounts>
<AdministratorPassword>
<Value><<img-password>></Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
</component>
</settings>
<settings pass="specialize">
</settings>
</unattend>
+4
View File
@@ -11,4 +11,8 @@ variable "vsphere_datastore" {}
variable "vsphere_network" {}
variable "vm_name" {}
variable "vm_guestos" {}
variable "winrm_password" {}
variable "repo_username" {}
variable "repo_password" {}
+133
View File
@@ -0,0 +1,133 @@
packer {
required_plugins {
windows-update = {
version = ">= 0.14.0"
source = "github.com/rgl/windows-update"
}
}
}
source "vsphere-iso" "win10" {
vcenter_server = var.vcenter_server
username = var.vsphere_username
password = var.vsphere_password
insecure_connection = "true"
vm_name = "${var.vm_guestos}-${var.vm_name}"
datacenter = var.vsphere_datacenter
host = var.vsphere_host
folder = var.vsphere_folder
datastore = var.vsphere_datastore
guest_os_type = "windows9_64Guest"
boot_order = "disk,cdrom"
boot_command = [""]
boot_wait = "5m"
communicator = "winrm"
winrm_username = "administrator"
winrm_password = var.winrm_password
winrm_timeout = "10m"
CPUs = 2
RAM = 8192
network_adapters {
network = var.vsphere_network
network_card = "vmxnet3"
}
storage {
disk_size = 20480
disk_thin_provisioned = true
}
disk_controller_type = ["lsilogic-sas"]
usb_controller = ["xhci"]
floppy_files = [
"packer/preseed/Windows10/Autounattend.xml",
"packer/preseed/Windows10/Sysprep_Unattend.xml",
"scripts/Set-NetworkProfile.ps1",
"scripts/Disable-WinRM.ps1",
"scripts/Enable-WinRM.ps1",
"scripts/Install-VMwareTools.cmd"
]
iso_checksum = "sha256:8D1663B71280533824CF95C7AB48ADAF5A187C38FCFF5B16A569F903688916D0"
iso_paths = [
"ISO-files/VMware-tools-windows-11.3.5-18557794/VMware-tools-windows-11.3.5-18557794.iso"
]
iso_url = "https://${var.repo_username}:${var.repo_password}@sn.itch.fyi/Repository/iso/Microsoft/Windows%2010/20H2/en_windows_10_enterprise_20H2_x64.iso"
shutdown_command = "C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /unattend:A:\\Sysprep_Unattend.xml"
shutdown_timeout = "1h"
export {
images = false
output_directory = "/scratch/win10"
}
remove_cdrom = true
}
build {
sources = ["source.vsphere-iso.win10"]
provisioner "windows-update" {
filters = [
"exclude:$_.Title -like '*Preview*'",
"include:$true"
]
}
provisioner "powershell" {
inline = [
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12",
"Invoke-Expression ((New-Object Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
]
}
provisioner "powershell" {
inline = [
"choco config set --name=limit-output --value=LimitOutput",
"choco install -y 7zip.install",
"choco install -y sysinternals",
"choco install -y firefox"
]
}
provisioner "windows-update" {
filters = [
"exclude:$_.Title -like '*Preview*'",
"include:$true"
]
}
provisioner "powershell" {
inline = [
"New-Item -Path 'C:\\Payload\\Scripts' -ItemType 'Directory' -Force:$True -Confirm:$False"
]
}
provisioner "file" {
destination = "C:\\Payload\\"
source = "scripts/Windows10/payload/"
}
provisioner "powershell" {
scripts = [
"scripts/Windows10/Register-ScheduledTask.ps1"
]
}
post-processor "shell-local" {
inline = [
"pwsh -command \"& scripts/Update-OvfConfiguration.ps1 \\",
" -OVFFile '/scratch/win10/${var.vm_guestos}-${var.vm_name}.ovf' \\",
" -Parameter @{'appliance.name'='${var.vm_guestos}';'appliance.version'='${var.vm_name}'}\"",
"pwsh -file scripts/Update-Manifest.ps1 \\",
" -ManifestFileName '/scratch/win10/${var.vm_guestos}-${var.vm_name}.mf'",
"ovftool --acceptAllEulas --allowExtraConfig --overwrite \\",
" '/scratch/win10/${var.vm_guestos}-${var.vm_name}.ovf' \\",
" /output/Windows10.ova"
]
}
}
-50
View File
@@ -1,50 +0,0 @@
[CmdletBinding()]
Param(
# No parameters
)
$InstallWindowsFeatureSplat = @{
Name = 'AD-Domain-Services', 'DHCP', 'RSAT-DNS-Server'
IncludeAllSubFeature = $True
IncludeManagementTools = $True
Restart = $False
Confirm = $False
}
Install-WindowsFeature @InstallWindowsFeatureSplat
$InstallPackageProviderSplat = @{
Name = 'NuGet'
MinimumVersion = '2.8.5.201'
Force = $True
Confirm = $False
}
Install-PackageProvider @InstallPackageProviderSplat
$SetPSRepositorySplat = @{
Name = 'PSGallery'
InstallationPolicy = 'Trusted'
}
Set-PSRepository @SetPSRepositorySplat
$InstallModuleSplat = @{
Name = 'powershell-yaml','gpwmifilter'
Force = $True
Confirm = $False
}
Install-Module @InstallModuleSplat
$SetPSRepositorySplat = @{
Name = 'PSGallery'
InstallationPolicy = 'Untrusted'
}
Set-PSRepository @SetPSRepositorySplat
# Double check whether the required PowerShell modules are available
$RequiredModules = @(
'powershell-yaml', # Provides cmdlets 'ConvertTo-Yaml' and 'ConvertFrom-Yaml'
'gpwmifilter', # Provides cmdlets '*-GPWmiFilter' and '*-GPWmiFilterAssignment'
'psframework' # Dependency for GMWmiFilter
)
ForEach ($Module in $RequiredModules) {
If ([boolean](Get-Module -Name $Module -ListAvailable) -ne $True) {
Write-Error -Message "Missing PowerShell module '$($Module)'"
Exit 1
}
}
@@ -1,86 +0,0 @@
[CmdletBinding()]
Param(
[Parameter()]
[string]$VaultAPIAddress,
[Parameter()]
[string]$VaultToken,
[Parameter()]
[string]$VaultPwPolicy,
[Parameter(Mandatory)]
[string]$VaultSecret,
[Parameter(Mandatory)]
[string]$Username
)
# Generate new password
$InvokeWebRequestSplat = @{
Uri = "$($VaultAPIAddress)/sys/policies/password/$($VaultPwPolicy)/generate"
Headers = @{'X-Vault-Token'="$VaultToken"}
UseBasicParsing = $True
}
$NewPassword = (Invoke-WebRequest @InvokeWebRequestSplat | ConvertFrom-Json).data.password
# Check for existense of secret
$Response, $ErrResponse = $Null, $Null
Try {
$InvokeWebRequestSplat = @{
Uri = "$($VaultAPIAddress)/secret/metadata/$($VaultSecret)"
Headers = @{'X-Vault-Token' = "$VaultToken"}
UseBasicParsing = $True
}
$Response = Invoke-WebRequest @InvokeWebRequestSplat
}
Catch [System.Net.WebException] {
$StreamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
$StreamReader.BaseStream.Position = 0
$ErrResponse = $StreamReader.ReadToEnd()
$StreamReader.Close()
}
If ([boolean]$Response) {
# Secret already exists; retrieve existing key/value pairs
$InvokeWebRequestSplat = @{
Uri = "$($VaultAPIAddress)/secret/data/$($VaultSecret)"
Headers = @{'X-Vault-Token' = "$VaultToken"}
UseBasicParsing = $True
}
$Secret = (Invoke-WebRequest @InvokeWebRequestSplat | ConvertFrom-Json).data
# Merge new password into dictionary
$AddMemberSplat = @{
MemberType = 'NoteProperty'
Name = "password.$($Username)"
Value = $NewPassword
Force = $True
}
$Secret.data | Add-Member @AddMemberSplat
# Store as new version
$InvokeWebRequestSplat = @{
Uri = "$($VaultAPIAddress)/secret/data/$($VaultSecret)"
Method = 'POST'
UseBasicParsing = $True
Headers = @{'X-Vault-Token'="$VaultToken"}
Body = @{
data = $Secret.data
} | ConvertTo-Json
}
Invoke-WebRequest @InvokeWebRequestSplat | Out-Null
}
ElseIf ([boolean]$ErrResponse) {
# Secret did not exist yet, store as new secret
$InvokeWebRequestSplat = @{
Uri = "$($VaultAPIAddress)/secret/data/$($VaultSecret)"
Method = 'POST'
UseBasicParsing = $True
Headers = @{'X-Vault-Token'="$VaultToken"}
Body = @{
data = @{
"password.$($Username)" = $NewPassword
}
} | ConvertTo-Json
}
Invoke-WebRequest @InvokeWebRequestSplat | Out-Null
}
Return $NewPassword
@@ -1,52 +0,0 @@
#Requires -Modules 'ActiveDirectory'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".yml")
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
$Entries = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$Entries = $YamlDocuments
}
ForEach ($OU in $Entries.OrganizationalUnits) {
$OUName, $OUPath = $OU.DistinguishedName -split ',', 2
If ($OUPath.Length -ne 0) {
$OUPath += ','
}
$NewADOrganizationalUnitSplat = @{
Name = $OUName.Substring(3)
Path = $OUPath + (Get-ADRootDSE).rootDomainNamingContext
Description = $OU.Description
ProtectedFromAccidentalDeletion = $False
ErrorAction = 'SilentlyContinue'
}
New-ADOrganizationalUnit @NewADOrganizationalUnitSplat
}
}
@@ -1,35 +0,0 @@
OrganizationalUnits:
- DistinguishedName: OU=Computer accounts
Description: ''
- DistinguishedName: OU=Clients,OU=Computer accounts
Description: ''
- DistinguishedName: OU=Desktops,OU=Clients,OU=Computer accounts
Description: ''
- DistinguishedName: OU=Laptops,OU=Clients,OU=Computer accounts
Description: ''
- DistinguishedName: OU=Kiosks,OU=Clients,OU=Computer accounts
Description: ''
- DistinguishedName: OU=Servers,OU=Computer accounts
Description: ''
- DistinguishedName: OU=Groups
Description: ''
- DistinguishedName: OU=Resources,OU=Groups
Description: ''
- DistinguishedName: OU=Roles,OU=Groups
Description: ''
- DistinguishedName: OU=User accounts
Description: ''
- DistinguishedName: OU=Privileged,OU=User accounts
Description: ''
- DistinguishedName: OU=Administrators,OU=Privileged,OU=User accounts
Description: ''
- DistinguishedName: OU=Service accounts,OU=Privileged,OU=User accounts
Description: ''
- DistinguishedName: OU=Non-privileged,OU=User accounts
Description: ''
- DistinguishedName: OU=Employees,OU=Non-privileged,OU=User accounts
Description: ''
- DistinguishedName: OU=Contractors,OU=Non-privileged,OU=User accounts
Description: ''
@@ -1,60 +0,0 @@
#Requires -Modules 'ActiveDirectory'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".yml")
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
$Entries = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$Entries = $YamlDocuments
}
ForEach ($Group in $Entries.SecurityGroups) {
$NewADGroupSplat = @{
Name = ($Group.DistinguishedName -split ',', 2)[0].Substring(3)
Path = ($Group.DistinguishedName -split ',', 2)[1] + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext)
Description = $Group.Description
GroupCategory = 'Security'
GroupScope = $Group.Scope
PassThru = $True
ErrorAction = 'SilentlyContinue'
}
$NewADGroup = New-ADGroup @NewADGroupSplat
If ([boolean]$Group.MemberOf) {
ForEach ($ParentGroup in $Group.MemberOf) {
$AddADGroupMemberSplat = @{
Identity = $ParentGroup + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext)
Members = $NewADGroup.DistinguishedName
ErrorAction = 'SilentlyContinue'
}
Add-ADGroupMember @AddADGroupMemberSplat
}
}
}
}
@@ -1,28 +0,0 @@
SecurityGroups:
# Resource groups
- DistinguishedName: CN=RemoteDesktop - Management servers,OU=Resources,OU=Groups
Description: ''
Scope: 'DomainLocal'
MemberOf: []
- DistinguishedName: CN=ContentLibraryAdmin - vSphere servers,OU=Resources,OU=Groups
Description: ''
Scope: 'DomainLocal'
MemberOf: []
- DistinguishedName: CN=DatastoreAdmin - vSphere servers,OU=Resources,OU=Groups
Description: ''
Scope: 'DomainLocal'
MemberOf: []
# Role groups
- DistinguishedName: CN=Hypervisor administrators,OU=Roles,OU=Groups
Description: ''
Scope: 'Global'
MemberOf:
- CN=RemoteDesktop - Management servers,OU=Resources,OU=Groups
- CN=DatastoreAdmin - vSphere servers,OU=Resources,OU=Groups
- CN=ContentLibraryAdmin - vSphere servers,OU=Resources,OU=Groups
- DistinguishedName: CN=Firewall administrators,OU=Roles,OU=Groups
Description: ''
Scope: 'Global'
MemberOf:
- CN=RemoteDesktop - Management servers,OU=Resources,OU=Groups
-69
View File
@@ -1,69 +0,0 @@
#Requires -Modules 'ActiveDirectory'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".yml")
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
$Entries = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$Entries = $YamlDocuments
}
ForEach ($User in $Entries.Users) {
$UserName = ($User.DistinguishedName -split ',', 2)[0].Substring(3)
$SanitizedUPN = ($UserName -replace "[^a-zA-Z0-9'\.-_!#\^~]").Trim('.')
# Create new user
$NewADUserSplat = @{
Name = $UserName
UserPrincipalName = "$($SanitizedUPN)@$((Get-ADDomain).DNSRoot)"
Path = ($User.DistinguishedName -split ',', 2)[1] + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext)
AccountPassword = ConvertTo-SecureString $User.Password -AsPlainText -Force
PassThru = $True
ErrorAction = 'SilentlyContinue'
}
$NewADUser = New-ADUser @NewADUserSplat
# Add user to group(s)
If ([boolean]$User.MemberOf) {
ForEach ($Group in $User.MemberOf) {
$AddADGroupMemberSplat = @{
Identity = $Group + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext)
Members = $NewADUser.DistinguishedName
ErrorAction = 'SilentlyContinue'
}
Add-ADGroupMember @AddADGroupMemberSplat
}
}
# Enable user
$EnableADAccountSplat = @{
Identity = $NewADUser.DistinguishedName
ErrorAction = 'Continue'
}
Enable-ADAccount @EnableADAccountSplat
}
}
-27
View File
@@ -1,27 +0,0 @@
Users:
- DistinguishedName: CN=Jane Doe,OU=Employees,OU=Non-privileged,OU=User accounts
Password: "{{ password.janedoe }}"
MemberOf: []
- DistinguishedName: CN=John Doe,OU=Contractors,OU=Non-privileged,OU=User accounts
Password: "{{ password.johndoe }}"
MemberOf: []
- DistinguishedName: CN=admJaneD,OU=Administrators,OU=Privileged,OU=User accounts
Password: "{{ password.admjaned }}"
MemberOf: []
- DistinguishedName: CN=zzLDAP,OU=Service accounts,OU=Privileged,OU=User accounts
Password: "{{ password.zzldap }}"
MemberOf: []
---
Variables:
- Name: password.janedoe
Expression: |
& ".\Provision-VaultPassword.ps1" -VaultSecret $Parameter['vault.secret'] -Username 'janedoe' -VaultAPIAddress $Parameter['vault.api'] -VaultToken $Parameter['vault.token'] -VaultPwPolicy $Parameter['vault.pwpolicy']
- Name: password.johndoe
Expression: |
& ".\Provision-VaultPassword.ps1" -VaultSecret $Parameter['vault.secret'] -Username 'johndoe' -VaultAPIAddress $Parameter['vault.api'] -VaultToken $Parameter['vault.token'] -VaultPwPolicy $Parameter['vault.pwpolicy']
- Name: password.admjaned
Expression: |
& ".\Provision-VaultPassword.ps1" -VaultSecret $Parameter['vault.secret'] -Username 'admjaned' -VaultAPIAddress $Parameter['vault.api'] -VaultToken $Parameter['vault.token'] -VaultPwPolicy $Parameter['vault.pwpolicy']
- Name: password.zzldap
Expression: |
& ".\Provision-VaultPassword.ps1" -VaultSecret $Parameter['vault.secret'] -Username 'zzldap' -VaultAPIAddress $Parameter['vault.api'] -VaultToken $Parameter['vault.token'] -VaultPwPolicy $Parameter['vault.pwpolicy']
@@ -1,133 +0,0 @@
#Requires -Modules 'ActiveDirectory'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$PSDrive = Get-PSDrive -Name 'AD'
If ([boolean]$PSDrive -eq $False) {
$NewPSDriveSplat = @{
Name = 'ADDS'
Root = ''
PSProvider = 'ActiveDirectory'
}
$PSDrive = New-PSDrive @NewPSDriveSplat
}
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".yml")
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
$Delegations = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$Delegations = $YamlDocuments
}
# Store GUIDs for all known AD schema classes
$GUIDMap, $GetADObjectSplat = @{}, @{
SearchBase = (Get-ADRootDSE).SchemaNamingContext
LDAPFilter = '(schemaidguid=*)'
Properties = 'lDAPDisplayName','schemaIDGUID'
}
Get-ADObject @GetADObjectSplat | ForEach-Object {
$GUIDMap[$_.lDAPDisplayName] = [GUID]$_.schemaIDGUID
}
# Store GUIDs for all extended rights
$GetADObjectSplat = @{
SearchBase = (Get-ADRootDSE).ConfigurationNamingContext
LDAPFilter = '(&(objectclass=controlAccessRight)(rightsguid=*))'
Properties = 'displayName','rightsGuid'
}
Get-ADObject @GetADObjectSplat | ForEach-Object {
$GUIDMap[$_.displayName] = [GUID]$_.rightsGuid
}
$GUIDMap['null'] = [Guid]::Empty
ForEach ($Entry in $Delegations.DelegationEntries) {
$GetADObjectSplat = @{
Filter = "sAMAccountName -eq '$($Entry.Principal)'"
Properties = 'objectSID'
}
$Principal = Get-ADObject @GetADObjectSplat
ForEach ($OU in $Entry.OrganizationalUnit) {
$GetADObjectSplat = @{
Identity = ($OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext))
ErrorAction = 'SilentlyContinue'
}
$OU = Get-ADObject @GetADObjectSplat
If ([boolean]$OU) {
$GetACLSPlat = @{
Path = "$($PSDrive.Name):\$($OU.DistinguishedName)"
}
$ACL = Get-ACL @GetACLSPlat
}
Else {
# Respective OU was not found in Active Directory; skipping permission assignment
Continue
}
ForEach ($Rule in $Entry.AccessRules) {
If ($Rule.ObjectType -eq '') {
$Rule.ObjectType = 'null'
}
If ($Rule.InheritedObjectType -eq '') {
$Rule.InheritedObjectType = 'null'
}
$NewACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
# An IdentityReference object that identifies the trustee of the access rule.
[System.Security.Principal.IdentityReference]$Principal.objectSID,
# A combination of one or more of the ActiveDirectoryRights enumeration values that specifies the rights of the access rule.
[System.DirectoryServices.ActiveDirectoryRights]$Rule.ActiveDirectoryRights,
# One of the AccessControlType enumeration values that specifies the access rule type.
[System.Security.AccessControl.AccessControlType]$Rule.AccessControlType,
# The schema GUID of the object to which the access rule applies.
[Guid]$GUIDMap[$Rule.ObjectType],
# One of the ActiveDirectorySecurityInheritance enumeration values that specifies the inheritance type of the access rule.
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$Rule.ActiveDirectorySecurityInheritance,
# The schema GUID of the child object type that can inherit this access rule.
[Guid]$GUIDMap[$Rule.InheritedObjectType]
)
$ACL.AddAccessRule($NewACE)
}
$SetAclSplat = @{
Path = "$($PSDrive.Name):\$($OU.DistinguishedName)"
AclObject = $ACL
ErrorAction = 'Continue'
}
Set-Acl @SetAclSplat
}
}
If ([boolean]($PSDrive.Name -eq 'ADDS') -eq $True) {
$RemovePSDriveSplat = @{
Name = 'ADDS'
Force = $True
Confirm = $False
}
Remove-PSDrive @RemovePSDriveSplat | Out-Null
}
}
@@ -1,76 +0,0 @@
DelegationEntries:
- Principal: admJaneD # Entries will be concatenated with ',DC=<example>,DC=<org>' automatically
OrganizationalUnit:
- CN=Computers
- OU=Kiosks,OU=Clients,OU=Computer accounts
AccessRules:
- ActiveDirectoryRights: Self # A combination of one or more of the ActiveDirectoryRights enumeration values that specifies the rights of the access rule.
AccessControlType: Allow # One of the AccessControlType enumeration values that specifies the access rule type.
ActiveDirectorySecurityInheritance: Descendents # One of the ActiveDirectorySecurityInheritance enumeration values that specifies the inheritance type of the access rule.
ObjectType: Validated write to DNS host name # The object type to which the access rule applies.
InheritedObjectType: Computer # The child object type that can inherit this access rule.
- ActiveDirectoryRights: Self
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: Validated write to service principal name
InheritedObjectType: Computer
- ActiveDirectoryRights: WriteProperty, WriteDacl
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: ''
InheritedObjectType: Computer
- ActiveDirectoryRights: ExtendedRight
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: Reset Password
InheritedObjectType: Computer
- ActiveDirectoryRights: ExtendedRight
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: Change Password
InheritedObjectType: Computer
- ActiveDirectoryRights: ReadProperty
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: ''
InheritedObjectType: Computer
- ActiveDirectoryRights: WriteProperty
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: ''
InheritedObjectType: Computer
- ActiveDirectoryRights: CreateChild, DeleteChild
AccessControlType: Allow
ActiveDirectorySecurityInheritance: All
ObjectType: Computer
InheritedObjectType: ''
- ActiveDirectoryRights: GenericAll
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: Computer
InheritedObjectType: ''
- Principal: admJaneD
OrganizationalUnit:
- OU=Clients,OU=Computer accounts
AccessRules:
- ActiveDirectoryRights: CreateChild, DeleteChild
AccessControlType: Allow
ActiveDirectorySecurityInheritance: All
ObjectType: User
InheritedObjectType: ''
- ActiveDirectoryRights: GenericAll
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: ''
InheritedObjectType: ''
- ActiveDirectoryRights: WriteProperty, ReadProperty
AccessControlType: Allow
ActiveDirectorySecurityInheritance: Descendents
ObjectType: Member
InheritedObjectType: Group
# ---
# Variables:
# - Name: foo
# Expression: |
# Write-Host 'bar'
@@ -1,65 +0,0 @@
Name: 'COMP: Firewall (Clients)'
LinkedOUs:
- OU=Clients,OU=Computer accounts
FirewallRules:
- Description: Rule A
Action: Block
Direction: Inbound
Program: ''
Port: '21-22,25'
Protocol: TCP
- Description: Rule B
Action: Allow
Direction: Inbound
Program: D:\MSSQL\sqlsvr.exe
Port: ''
Protocol: ''
FirewallProfiles:
- Name: Domain
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\domainfw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
- Name: Private
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\privatefw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
- Name: Public
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\publicfw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
# ---
# Variables:
# - Name: foo
# Expression: |
# Write-Host 'bar'
@@ -1,65 +0,0 @@
Name: 'COMP: Firewall (DomainControllers)'
LinkedOUs:
- OU=Domain Controllers
FirewallRules:
- Description: Rule A
Action: Block
Direction: Inbound
Program: ''
Port: '21-22,25'
Protocol: TCP
- Description: Rule B
Action: Allow
Direction: Inbound
Program: D:\MSSQL\sqlsvr.exe
Port: ''
Protocol: ''
FirewallProfiles:
- Name: Domain
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\domainfw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
- Name: Private
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\privatefw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
- Name: Public
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\publicfw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
# ---
# Variables:
# - Name: foo
# Expression: |
# Write-Host 'bar'
@@ -1,140 +0,0 @@
#Requires -Modules 'NetSecurity'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$GetItemSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', '.yml')
}
ForEach ($File in (Get-Item @GetItemSplat)) {
Try {
Write-Host "Loading/parsing file '$($File)' ..."
$GetContentSplat = @{
Path = $File
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
}
Catch {
$ParseErrors += "While processing '$($File)': $($_.Exception.Message)"
Continue
}
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
Try {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
}
Catch {
$ParseErrors += "While processing '$($File)' (after substitutions): $($_.Exception.Message)"
Continue
}
$Settings = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$Settings = $YamlDocuments
}
$NewGPOSplat = @{
Name = $Settings.Name
}
$NewGPO = New-GPO @NewGPOSplat
$OpenNetGPOSplat = @{
PolicyStore = "$($Parameter['addsconfig.domainname'])\$($NewGPO.DisplayName)"
}
$GPOSession = Open-NetGPO @OpenNetGPOSplat
ForEach ($Rule in $Settings.FirewallRules) {
$NewNetFirewallRuleSplat = @{
# Using so-called string formatting with the '-f' operator (looks more complicated than it is) to create consistent policy names:
# Examples:
# 'DENY: Inbound port 443 (TCP)'
# 'ALLOW: Inbound 'D:\MSSQL\bin\sqlservr.exe'
DisplayName = ("{0}: {1} {2} {3} {4}" -f
$Rule.Action.ToUpper(),
$Rule.Direction,
("'$($Rule.Program)'", $NULL)[!($Rule.Program)],
("Port $($Rule.Port)", $NULL)[!($Rule.Port)],
("($($Rule.Protocol))", $NULL)[!($Rule.Protocol)]
) -replace '\s+',' '
Description = $Rule.Description
Action = $Rule.Action
Direction = $Rule.Direction
Program = ($Rule.Program, 'Any')[!($Rule.Program)]
LocalPort = ($Rule.Port.Split(','), 'Any')[!($Rule.Port)]
Protocol = ($Rule.Protocol, 'Any')[!($Rule.Protocol)]
GPOSession = $GPOSession
PolicyStore = $NewGPO.DisplayName
Confirm = $False
}
New-NetFirewallRule @NewNetFirewallRuleSplat
}
ForEach ($Profile in $Settings.FirewallProfiles) {
$SetNetFirewallProfileSplat = @{
Name = $Profile.Name
Enabled = $Profile.Enabled
DefaultInboundAction = $Profile.Connections.Inbound
DefaultOutboundAction = $Profile.Connections.Outbound
LogAllowed = $Profile.Logging.LogSuccessfullConnections
LogBlocked = $Profile.Logging.LogDroppedPackets
LogFileName = $Profile.Logging.Name
LogMaxSizeKilobytes = $Profile.Logging.SizeLimit
AllowLocalFirewallRules = $Profile.Settings.ApplyLocalFirewallRules
AllowLocalIPsecRules = $Profile.Settings.ApplyLocalConnectionSecurityRules
NotifyOnListen = $Profile.Settings.DisplayNotification
GPOSession = $GPOSession
PolicyStore = $NewGPO.DisplayName
Confirm = $False
}
Set-NetFirewallProfile @SetNetFirewallProfileSplat
}
$SaveNetGPOSplat = @{
GPOSession = $GPOSession
}
Save-NetGPO @SaveNetGPOSplat
ForEach ($OU in $Settings.LinkedOUs) {
If (Test-Path "AD:\$($OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext))") {
Try {
Write-Host "Linking policy '$($NewGPO.DisplayName)' to OU '$($OU)' ..."
$NewGPLinkSplat = @{
Name = $NewGPO.DisplayName
Target = $OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext)
}
New-GPLink @NewGPLinkSplat | Out-Null
}
Catch {
$ParseErrors += "Could not link GPO '$($NewGPO.DisplayName)' to OU '$($OU)'"
Continue
}
}
Else {
$ParseErrors += "Path not accessible (referred to by '$($NewGPO.DisplayName)'): 'AD:\$($OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext))'"
Continue
}
}
}
If ($ParseErrors) {
Throw "One or more errors occurred:`n$($ParseErrors -join "`n")"
}
}
@@ -1,65 +0,0 @@
Name: 'COMP: Firewall (Servers)'
LinkedOUs:
- OU=Servers,OU=Computer accounts
FirewallRules:
- Description: Rule A
Action: Block
Direction: Inbound
Program: ''
Port: '21-22,25'
Protocol: TCP
- Description: Rule B
Action: Allow
Direction: Inbound
Program: D:\MSSQL\sqlsvr.exe
Port: ''
Protocol: ''
FirewallProfiles:
- Name: Domain
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\domainfw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
- Name: Private
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\privatefw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
- Name: Public
Enabled: 'True'
Connections:
Inbound: Block
Outbound: Allow
Settings:
DisplayNotification: 'False'
ApplyLocalFirewallRules: 'True'
ApplyLocalConnectionSecurityRules: 'True'
Logging:
Name: '%SYSTEMROOT%\System32\Logfiles\Firewall\publicfw.log'
SizeLimit: 16384
LogDroppedPackets: 'True'
LogSuccessfullConnections: 'False'
# ---
# Variables:
# - Name: foo
# Expression: |
# Write-Host 'bar'
@@ -1,27 +0,0 @@
#Requires -Modules 'DhcpServer'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Configure DHCP (if and only if this server is not already an authorized DHCP server)
If ((Get-DHCPServerInDC).IPAddress -NotContains $Parameter['guestinfo.ipaddress']) {
# Add DHCP security groups
& netsh dhcp add securitygroups
# Authorize DHCP server
$AddDhcpServerInDCSplat = @{
DnsName = "$($Parameter['guestinfo.hostname']).$($Parameter['addsconfig.domainname'])"
IPAddress = $($Parameter['guestinfo.ipaddress'])
Confirm = $False
}
Add-DhcpServerInDC @AddDhcpServerInDCSplat
# Notify Server Manager post-install configuration has completed
$SetItemPropertySplat = @{
Path = 'HKLM:\SOFTWARE\Microsoft\ServerManager\Roles\12'
Name = 'ConfigurationState'
Value = 2
}
Set-ItemProperty @SetItemPropertySplat
}
@@ -1,54 +0,0 @@
#Requires -Modules 'DhcpServer'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on secondary or standalone Domain Controller
If (@('secondary','standalone') -contains $Parameter['deployment.type']) {
$AddDhcpServerv4ScopeSplat = @{
Name = 'Default DHCP scope'
StartRange = [ipaddress]$Parameter['dhcpconfig.startip']
EndRange = [ipaddress]$Parameter['dhcpconfig.endip']
SubnetMask = [ipaddress]$Parameter['dhcpconfig.subnetmask']
LeaseDuration = [timespan]$Parameter['dhcpconfig.leaseduration']
State = 'Active'
PassThru = $True
Confirm = $False
}
$DhcpScope = Add-DhcpServerv4Scope @AddDhcpServerv4ScopeSplat
$ScopeOptions = @(
@{
# 003 Router
OptionId = 3
Value = $Parameter['dhcpconfig.gateway']
},
@{
# 004 Time Server
OptionId = 4
Value = (Resolve-DnsName -Name $Parameter['addsconfig.domainname']).IPAddress
},
@{
# 006 DNS Server
OptionId = 6
Value = (Resolve-DnsName -Name $Parameter['addsconfig.domainname']).IPAddress
},
@{
# 015 DNS Domain Name
OptionId = 15
Value = $Parameter['addsconfig.domainname']
}
)
ForEach ($Option in $ScopeOptions) {
$SetDhcpServerv4OptionValueSplat = @{
ScopeId = $DhcpScope.ScopeId
OptionId = $Option.OptionId
Value = $Option.Value
Force = $True
Confirm = $False
}
Set-DhcpServerv4OptionValue @SetDhcpServerv4OptionValueSplat
}
}
@@ -1,42 +0,0 @@
#Requires -Modules 'DhcpServer'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on secondary Domain Controller
If ($Parameter['deployment.type'] -eq 'secondary') {
# Wait for secondary DHCP server to be registered in DNS
$Timestamp, $TimeoutMinutes = (Get-Date), 5
Do {
If ($Timestamp.AddMinutes($TimeoutMinutes) -lt (Get-Date)) {
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'OVF-Properties'
EntryType = 'Warning'
EventID = 13
Message = "Timeout after $($TimeoutMinutes) minutes waiting for secondary Domain Controller to be registered in DNS."
}
Write-EventLog @WriteEventLogSplat
Break
}
Start-Sleep -Seconds 5
} Until ((Get-DhcpServerInDC).Count -gt 1)
$NewCimSessionSplat = @{
Credential = New-Object System.Management.Automation.PSCredential(
(Get-ADUser -Filter * | Where-Object {$_.SID -match '-500'}).SamAccountName,
(ConvertTo-SecureString $Parameter['addsconfig.administratorpw'] -AsPlainText -Force)
)
}
$AddDhcpServerv4FailoverSplat = @{
Name = 'Failover #42'
PartnerServer = (Get-DhcpServerInDC).DnsName | Where-Object {$_ -ne "$($Parameter['guestinfo.hostname']).$($Parameter['addsconfig.domainname'])"}
ServerRole = 'Active'
ScopeId = (Get-DhcpServerv4Scope).ScopeId.IPAddressToString
CimSession = New-CimSession @NewCimSessionSplat
}
Add-DhcpServerv4Failover @AddDhcpServerv4FailoverSplat
}
@@ -1,88 +0,0 @@
#Requires -Modules 'DnsServer'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on secondary or standalone Domain Controller
If (@('secondary','standalone') -contains $Parameter['deployment.type']) {
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".$($Parameter['deployment.type']).yml")
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression -ErrorAction 'SilentlyContinue')
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
$Records = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$Records = $YamlDocuments
}
ForEach ($Record in $Records.Entries) {
$AddDnsServerResourceRecordSplat = @{
ComputerName = $Parameter['guestinfo.dnsserver']
ZoneName = $Parameter['addsconfig.domainname']
Name = [string]$Record.Name
TimeToLive = (New-TimeSpan -Hours 1)
AgeRecord = $False
Confirm = $False
}
Switch ($Record.Type) {
'A' {
$AddDnsServerResourceRecordSplat.Add('A', $True)
$AddDnsServerResourceRecordSplat.Add('IPv4Address', $Record.Value)
}
'AAAA' {
$AddDnsServerResourceRecordSplat.Add('AAAA', $True)
$AddDnsServerResourceRecordSplat.Add('IPv6Address', $Record.Value)
}
'CNAME' {
$AddDnsServerResourceRecordSplat.Add('CNAME', $True)
$AddDnsServerResourceRecordSplat.Add('HostNameAlias', $Record.Value)
}
'MX' {
$AddDnsServerResourceRecordSplat.Add('MX', $True)
# Value should match pattern '<fqdn>:<preference>'
# ie. 'mail.contoso.com:10'
$MailExch = $Record.Value -split ':'
$AddDnsServerResourceRecordSplat.Add('MailExchange', $MailExch[0])
$AddDnsServerResourceRecordSplat.Add('Preference', $MailExch[1])
}
'NS' {
$AddDnsServerResourceRecordSplat.Add('NS', $True)
$AddDnsServerResourceRecordSplat.Add('NameServer', $Record.Value)
}
'SRV' {
$AddDnsServerResourceRecordSplat.Add('SRV', $True)
# Value should match pattern '<fqdn>:<priority>:<weight>:<port>'
# ie. 'sipserver.contoso.com:0:0:5060'
$SrvLocator = $Record.Value -split ':'
$AddDnsServerResourceRecordSplat.Add('DomainName', $SrvLocator[0])
$AddDnsServerResourceRecordSplat.Add('Priority', $SrvLocator[1])
$AddDnsServerResourceRecordSplat.Add('Weight', $SrvLocator[2])
$AddDnsServerResourceRecordSplat.Add('Port', $SrvLocator[3])
}
'TXT' {
$AddDnsServerResourceRecordSplat.Add('TXT', $True)
$AddDnsServerResourceRecordSplat.Add('DescriptiveText', $Record.Value)
}
}
Add-DnsServerResourceRecord @AddDnsServerResourceRecordSplat
}
}
@@ -1,27 +0,0 @@
Entries:
- Name: ldap
Type: A
Value: "{{ primarydc }}"
- Name: ldap
Type: A
Value: "{{ secondarydc }}"
- Name: timeserver
Type: A
Value: "{{ primarydc }}"
- Name: timeserver
Type: A
Value: "{{ secondarydc }}"
# - Name: mail
# Type: MX
# Value: mail.contoso.com:10 # Value should match pattern '<fqdn>:<preference>'
# - Name: voipserver
# Type: SRV
# Value: sip.contoso.com:0:0:5060 # Value should match pattern '<fqdn>:<priority>:<weight>:<port>'
---
Variables:
- Name: primarydc
Expression: |
(Resolve-DnsName -Name $Parameter['addsconfig.domainname'] | Sort-Object)[0].IPAddress
- Name: secondarydc
Expression: |
(Resolve-DnsName -Name $Parameter['addsconfig.domainname'] | Sort-Object)[1].IPAddress
@@ -1,18 +0,0 @@
Entries:
- Name: ldap
Type: A
Value: "{{ primarydc }}"
- Name: timeserver
Type: A
Value: "{{ primarydc }}"
# - Name: mail
# Type: MX
# Value: mail.contoso.com:10 # Value should match pattern '<fqdn>:<preference>'
# - Name: voipserver
# Type: SRV
# Value: sip.contoso.com:0:0:5060 # Value should match pattern '<fqdn>:<priority>:<weight>:<port>'
---
Variables:
- Name: primarydc
Expression: |
(Resolve-DnsName -Name $Parameter['addsconfig.domainname'] | Sort-Object)[0].IPAddress
@@ -1,47 +0,0 @@
#Requires -Modules 'GPWmiFilter'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', '.yml')
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
$WmiFilters = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$WmiFilters = $YamlDocuments
}
ForEach ($Filter in $WmiFilters) {
$NewGPWmiFilterSplat = @{
Name = $Filter.Name
Description = $Filter.Description
Expression = $Filter.Expressions
Server = $Parameter['addsconfig.domainname']
ErrorAction = 'SilentlyContinue'
}
New-GPWmiFilter @NewGPWmiFilterSplat
}
}
@@ -1,9 +0,0 @@
- Name: PDC Emulator
Description: Primary Domain Controller Emulator only
Expressions:
- 'SELECT * FROM Win32_ComputerSystem WHERE DomainRole = 5'
# ---
# Variables:
# - Name: foo
# Expression: |
# Write-Host 'bar'
@@ -1,15 +0,0 @@
Name: 'COMP: Disable Server Manager at Logon'
Type: Object
LinkedOUs:
- OU=Servers,OU=Computer accounts
- OU=Domain Controllers
WMIFilters: []
RegistryEntries:
- Key: HKLM\Software\Microsoft\ServerManager
Type: Dword
ValueName: DoNotOpenAtServerManagerAtLogon
Value: 1
- Key: HKLM\Software\Microsoft\ServerManager
Type: Dword
ValueName: DoNotPopWACConsoleAtSMLaunch
Value: 1
@@ -1,19 +0,0 @@
Name: 'COMP: Loopback processing (Merge)'
Type: Object
LinkedOUs: []
WMIFilters: []
RegistryEntries:
- Key: HKLM\Software\Policies\Microsoft\Windows\System
Type: Dword
ValueName: UserPolicyMode
Value: 1
---
Name: 'COMP: Loopback processing (Replace)'
Type: Object
LinkedOUs: []
WMIFilters: []
RegistryEntries:
- Key: HKLM\Software\Policies\Microsoft\Windows\System
Type: Dword
ValueName: UserPolicyMode
Value: 2
@@ -1,36 +0,0 @@
Name: 'COMP: Timeserver configuration (W32Time)'
Type: Object
LinkedOUs:
- OU=Domain Controllers
WMIFilters:
- PDC Emulator
RegistryEntries:
- Key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters
Type: String
ValueName:
- Type
- NtpServer
Value:
- NTP
- "{{ addsconfig.ntpserver }}"
- Key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config
Type: DWord
ValueName: AnnounceFlags
Value: 0xA
- Key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config
Type: DWord
ValueName: MaxPosPhaseCorrection
Value: 0xFFFFFFFF
- Key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config
Type: DWord
ValueName: MaxNegPhaseCorrection
Value: 0xFFFFFFFF
- Key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer
Type: DWord
ValueName: Enabled
Value: 1
---
Variables:
- Name: addsconfig.ntpserver
Expression: |
($Parameter['addsconfig.ntpserver'] -split ',' | ForEach-Object {'{0},0x1' -f $_}) -join ' '
@@ -1,116 +0,0 @@
Name: 'COMP: Restrict Internet Communication'
Type: Object
LinkedOUs:
- OU=Servers,OU=Computer accounts
WMIFilters: []
RegistryEntries:
- Key: HKLM\Software\Policies\Microsoft\InternetManagement
Type: DWord
ValueName: RestrictCommunication
Value: 1
# All below settings are set such that their respective features cannot access the Internet
# If any of these settings are in conflict with the above setting, gpmc.msc will behave erratic!
- Key: HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
Type: Dword
ValueName: NoPublishingWizard
Value: 1
- Key: HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
Type: Dword
ValueName: NoWebServices
Value: 1
- Key: HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
Type: DWord
ValueName: NoOnlinePrintsWizard
Value: 1
- Key: HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
Type: DWord
ValueName: NoInternetOpenWith
Value: 1
- Key: HKLM\Software\Policies\Microsoft\EventViewer
Type: DWord
ValueName: MicrosoftEventVwrDisableLinks
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Messenger\Client
Type: DWord
ValueName: CEIP
Value: 2
- Key: HKLM\Software\Policies\Microsoft\PCHealth\ErrorReporting
Type: DWord
ValueName: DoReport
Value: 0
- Key: HKLM\Software\Policies\Microsoft\PCHealth\HelpSvc
Type: DWord
ValueName: Headlines
Value: 0
- Key: HKLM\Software\Policies\Microsoft\PCHealth\HelpSvc
Type: DWord
ValueName: MicrosoftKBSearch
Value: 0
- Key: HKLM\Software\Policies\Microsoft\SearchCompanion
Type: DWord
ValueName: DisableContentFileUpdates
Value: 1
- Key: HKLM\Software\Policies\Microsoft\SystemCertificates\AuthRoot
Type: DWord
ValueName: DisableRootAutoUpdate
Value: 1
- Key: HKLM\Software\Policies\Microsoft\SQMClient\Windows
Type: DWord
ValueName: CEIPEnable
Value: 0
- Key: HKLM\Software\Policies\Microsoft\Windows\DriverSearching
Type: DWord
ValueName: DontSearchWindowsUpdate
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\HandwritingErrorReports
Type: DWord
ValueName: PreventHandwritingErrorReports
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\Internet Connection Wizard
Type: DWord
ValueName: ExitOnMSICW
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\NetworkConnectivityStatusIndicator
Type: Dword
ValueName: NoActiveProbe
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\Registration Wizard Control
Type: DWord
ValueName: NoRegistration
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\TabletPC
Type: DWord
ValueName: PreventHandwritingDataSharing
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\Windows Error Reporting
Type: DWord
ValueName: Disabled
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate
Type: DWord
ValueName: DisableWindowsUpdateAccess
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows NT\CurrentVersion\Software Protection Platform
Type: DWord
ValueName: NoGenTicket
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows NT\Printers
Type: DWord
ValueName: DisableHTTPPrinting
Value: 1
- Key: HKLM\Software\Policies\Microsoft\Windows NT\Printers
Type: DWord
ValueName: DisableWebPnPDownload
Value: 1
- Key: HKLM\Software\Policies\Microsoft\WindowsMovieMaker
Type: DWord
ValueName: WebHelp
Value: 1
- Key: HKLM\Software\Policies\Microsoft\WindowsMovieMaker
Type: DWord
ValueName: CodecDownload
Value: 1
- Key: HKLM\Software\Policies\Microsoft\WindowsMovieMaker
Type: DWord
ValueName: WebPublish
Value: 1
@@ -1,44 +0,0 @@
Name: 'COMP: Example GPO' # Prefix the name with either 'COMP:' or 'USER:'
Type: Object # Either 'Object' or 'Preference' (respectively for GPO or GPP)
LinkedOUs: # Entries will be concatenated with ',DC=<example>,DC=<org>' automatically
- OU=Servers
WMIFilters:
- FilterA
- FilterB
RegistryEntries:
- Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\System
Type: DWord
ValueName: PropertyA
Value: 1
- Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\System
Type: DWord
ValueName: PropertyB
Value: 0xFFFFFFFF # Hexadecimal values are prefixed with '0x'
- Key: HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters
Type: String
ValueName: # Multiple entries are possible, but *only* for the data type 'String' and 'ExpandString' (REG_SZ and REG_EXPAND_SZ)
- PropertyP
- PropertyQ
- PropertyR
Value: # The amount of entries must match with 'ValueName'
- ValueP
- ValueQ
- ValueR
- Key: HKLM\Software\Test
Type: String
ValueName:
- PropertyX
- PropertyDate
- PropertyOVF
Value: # Values can contain variablenames (respective entries must be declared under 'Variables' below)
- ValueX
- "{{ date }}"
- "{{ guestinfo.dnsserver }}"
---
Variables: # Each variable consists of a name that is used as a placeholder in the yaml file above, and a PowerShell expression
- Name: date
Expression: | # The PowerShell script's output must evaluate to a [string]
Get-Date
- Name: guestinfo.dnsserver
Expression: | # The variable '$Parameter' will automatically contain all defined OVF Properties
$Parameter['guestinfo.dnsserver']
@@ -1,34 +0,0 @@
Name: 'COMP: Example GPO' # Prefix the name with either 'COMP:' or 'USER:'
Type: Preference # Either 'Object' or 'Preference' (respectively for GPO or GPP)
LinkedOUs: # Entries will be concatenated with ',DC=<example>,DC=<org>' automatically
- OU=Servers
WMIFilters:
- FilterA
- FilterB
RegistryEntries:
- Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\System
Type: DWord
ValueName: PropertyA
Value: 1
Action: Replace # Valid values are: Create, Update, Replace or Delete
Context: Computer # Valid values are: User or Computer
Disable: False # Change to 'True' when GPP entry should not be applied
- Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\System
Type: DWord
ValueName: PropertyB
Value: 0xFFFFFFFF # Hexadecimal values are prefixed with '0x'
Action: Replace
Context: Computer
Disable: False
- Key: HKLM\Software\Test
Type: String
ValueName: PropertyOVF
Value: "{{ guestinfo.dnsserver }}" # Values can contain variablenames (respective entries must be declared under 'Variables' below)
Action: Replace
Context: Computer
Disable: False
---
Variables: # Each variable consists of a name that is used as a placeholder in the yaml file above, and a PowerShell expression
- Name: guestinfo.dnsserver
Expression: | # The variable '$Parameter' will automatically contain all defined OVF Properties
$Parameter['guestinfo.dnsserver']
-201
View File
@@ -1,201 +0,0 @@
#Requires -Modules 'powershell-yaml'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$NewPSSessionSplat = @{
ComputerName = $Parameter['guestinfo.hostname']
Credential = New-Object System.Management.Automation.PSCredential(
(Get-ADUser -Filter * | Where-Object {$_.SID -match '-500'}).SamAccountName,
(ConvertTo-SecureString $Parameter['addsconfig.administratorpw'] -AsPlainText -Force)
)
}
$PSSession = New-PSSession @NewPSSessionSplat
$ParseErrors = @()
$GetItemSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', '.*.yml')
}
ForEach ($File in (Get-Item @GetItemSplat)) {
Try {
Write-Host "Loading/parsing file '$($File)' ..."
$GetContentSplat = @{
Path = $File
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
}
Catch {
$ParseErrors += "While processing '$($File)': $($_.Exception.Message)"
Continue
}
# Check if the respective .yml file declared substitutions which need to be parsed
If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) {
Try {
ForEach ($Pattern in $YamlDocuments[-1].Variables) {
$RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression)
}
# Perform conversion to Yaml again, now with parsed file contents
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat
}
Catch {
$ParseErrors += "While processing '$($File)' (after substitutions): $($_.Exception.Message)"
Continue
}
$GroupPolicies = $YamlDocuments[0..($YamlDocuments.Count - 2)]
}
Else {
$GroupPolicies = $YamlDocuments
}
ForEach ($GroupPolicy in $GroupPolicies) {
Write-Host "Initiating policy '$($GroupPolicy.Name)' ..."
$NewGPOSplat = @{
Name = $GroupPolicy.Name
ErrorAction = 'SilentlyContinue'
ErrorVariable = 'Failure'
}
$NewGPO = New-GPO @NewGPOSplat
If ($Failure) {
Continue
}
Switch ($GroupPolicy.Type) {
'Object' {
ForEach ($ValueSet in $GroupPolicy.RegistryEntries) {
Write-Host "Adding key/value to policy '$($NewGPO.DisplayName)' ...`n [$($ValueSet.Key)/$($ValueSet.ValueName)]"
$SetGPRegistryValueSplat = @{
Name = $NewGPO.DisplayName
Key = $ValueSet.Key
ValueName = $ValueSet.ValueName
Type = $ValueSet.Type
Value = Switch ($ValueSet.Type) {
'Binary' {
# Accepted formats:
# 000A0F0100
# 00 0A 0F 01 00
# 00,0A,0F,01,00
[byte[]]([regex]::split(($ValueSet.Value -replace '[ ,]'), '([0-9a-eA-E]{2})') | Where-Object {$_} | ForEach-Object {'0x{0}' -f $_})
}
'DWord' {
[uint32]$ValueSet.Value
}
'QWord' {
[uint64]$ValueSet.Value
}
Default {
$ValueSet.Value
}
}
ErrorAction = 'SilentlyContinue'
}
Set-GPRegistryValue @SetGPRegistryValueSplat | Out-Null
}
}
'Preference' {
ForEach ($ValueSet in $GroupPolicy.RegistryEntries) {
Write-Host "Adding key/value to policy '$($NewGPO.DisplayName)' ...`n [$($ValueSet.Key)/$($ValueSet.ValueName)]"
$SetGPPrefRegistryValueSplat = @{
Name = $NewGPO.DisplayName
Key = $ValueSet.Key
Context = $ValueSet.Context
Action = $ValueSet.Action
ValueName = $ValueSet.ValueName
Type = $ValueSet.Type
Value = Switch ($ValueSet.Type) {
'Binary' {
# Accepted formats:
# 000A0F0100
# 00 0A 0F 01 00
# 00,0A,0F,01,00
[byte[]]([regex]::split(($ValueSet.Value -replace '[ ,]'), '([0-9a-eA-E]{2})') | Where-Object {$_} | ForEach-Object {'0x{0}' -f $_})
}
'DWord' {
[uint32]$ValueSet.Value
}
'QWord' {
[uint64]$ValueSet.Value
}
Default {
$ValueSet.Value
}
}
Disable = [Convert]::ToBoolean($ValueSet.Disable)
ErrorAction = 'SilentlyContinue'
}
Set-GPPrefRegistryValue @SetGPPrefRegistryValueSplat | Out-Null
}
}
}
ForEach ($Filter in $GroupPolicy.WMIFilters) {
$InvokeCommandSplat = @{
Session = $PSSession
ArgumentList = $Filter, $Parameter, $NewGPO
ScriptBlock = {
#Requires -Modules 'GPWmiFilter'
Param(
$Filter,
$Parameter,
$NewGPO
)
$GetGPWmiFilterSplat = @{
Name = $Filter
Server = $Parameter['addsconfig.domainname']
ErrorAction = 'SilentlyContinue'
}
If (Get-GPWMIFilter @GetGPWmiFilterSplat) {
$SetGPWmiFilterAssignmentSplat = @{
Policy = $NewGPO
Filter = $Filter
EnableException = $True
ErrorAction = 'SilentlyContinue'
}
Set-GPWmiFilterAssignment @SetGPWmiFilterAssignmentSplat
}
}
}
Invoke-Command @InvokeCommandSplat
}
ForEach ($OU in $GroupPolicy.LinkedOUs) {
If (Test-Path "AD:\$($OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext))") {
Try {
Write-Host "Linking policy '$($NewGPO.DisplayName)' to OU '$($OU)' ..."
$NewGPLinkSplat = @{
Name = $NewGPO.DisplayName
Target = $OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext)
}
New-GPLink @NewGPLinkSplat | Out-Null
}
Catch {
$ParseErrors += "Could not link GPO '$($NewGPO.DisplayName)' to OU '$($OU)'"
Continue
}
}
Else {
$ParseErrors += "Path not accessible (referred to by '$($NewGPO.DisplayName)'): 'AD:\$($OU + (',{0}' -f (Get-ADRootDSE).rootDomainNamingContext))'"
Continue
}
}
}
}
If ($ParseErrors) {
Throw "One or more errors occurred:`n$($ParseErrors -join "`n")"
}
}
@@ -1,83 +0,0 @@
#Requires -Modules 'ActiveDirectory','powershell-yaml'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$PSDrive = Get-PSDrive -Name 'AD'
If ([boolean]$PSDrive -eq $False) {
$NewPSDriveSplat = @{
Name = 'ADDS'
Root = ''
PSProvider = 'ActiveDirectory'
}
$PSDrive = New-PSDrive @NewPSDriveSplat
}
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', '.yml')
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$WhiteList = ConvertFrom-Yaml @ConvertFromYamlSplat
$GetADObjectSplat = @{
Filter = '*'
SearchBase = 'DC=' + $Parameter['addsconfig.domainname'].Replace('.', ',DC=')
SearchScope = 'OneLevel'
}
$WhiteListedOUs = @()
ForEach ($OU in $WhiteList.WhiteListedOUs) {
$WhiteListedOUs += Get-ADObject @GetADObjectSplat | Where-Object {
$_.DistinguishedName -match $OU
}
}
$ParentContainers = Get-ADObject @GetADObjectSplat | Where-Object {
('builtinDomain', 'container', 'organizationalUnit', <#'lostAndFound',#> 'msDS-QuotaContainer', 'msTPM-InformationObjectsContainer') -contains $_.ObjectClass
}
ForEach ($Parent in $ParentContainers) {
If ($WhiteListedOUs.DistinguishedName -notcontains $Parent.DistinguishedName) {
ForEach ($SecurityPrincipal in $WhiteList.LimitedSecurityPrincipals) {
$GetACLSPlat = @{
Path = "$($PSDrive.Name):\$($Parent.DistinguishedName)"
}
$ACL = Get-ACL @GetACLSPlat
$GetADObjectSplat = @{
Filter = "sAMAccountName -eq '$($SecurityPrincipal)'"
Properties = 'objectSID'
}
$NewACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
(Get-ADObject @GetADObjectSplat).objectSID,
[System.DirectoryServices.ActiveDirectoryRights]"GenericAll",
[System.Security.AccessControl.AccessControlType]"Deny",
[System.DirectoryServices.ActiveDirectorySecurityInheritance]"All"
)
$ACL.AddAccessRule($NewACE)
$SetAclSplat = @{
Path = "$($PSDrive.Name):\$($Parent.DistinguishedName)"
AclObject = $ACL
ErrorAction = 'Continue'
}
Set-Acl @SetAclSplat
}
}
}
If ([boolean]$PSDrive.Name -eq 'ADDS') {
$RemovePSDriveSplat = @{
Name = 'ADDS'
Force = $True
Confirm = $False
}
Remove-PSDrive @RemovePSDriveSplat | Out-Null
}
}
@@ -1,4 +0,0 @@
WhiteListedOUs: [] # Entries will be concatenated with ',DC=<example>,DC=<org>' automatically
#- OU=User accounts
LimitedSecurityPrincipals: []
#- Servicedesk employees
@@ -1,34 +0,0 @@
#Requires -Modules 'ActiveDirectory'
Param(
[Parameter(Mandatory)]
[hashtable]$Parameter
)
# Only executed on primary or standalone Domain Controller
If (@('primary','standalone') -contains $Parameter['deployment.type']) {
$GetContentSplat = @{
Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".yml")
Raw = $True
}
$RawContent = Get-Content @GetContentSplat
$ConvertFromYamlSplat = @{
Yaml = $RawContent
AllDocuments = $True
}
$Policy = ConvertFrom-Yaml @ConvertFromYamlSplat
$SetADDefaultDomainPasswordPolicySplat = @{
Identity = $Parameter['addsconfig.domainname']
ComplexityEnabled = [Convert]::ToBoolean($Policy.Password.RequireComplexity)
LockoutThreshold = [uint32]$Policy.Account.Lockout.Threshold
# LockoutDuration = [timespan]$Policy.Account.Lockout.Duration
# LockoutObservationWindow = [timespan]$Policy.Account.Lockout.ObservationWindow
MaxPasswordAge = [timespan]$Policy.Password.Age.Maximum
MinPasswordAge = [timespan]$Policy.Password.Age.Minimum
MinPasswordLength = [uint32]$Policy.Password.Length.Minimum
PasswordHistoryCount = [uint32]$Policy.Password.History
ReversibleEncryptionEnabled = [Convert]::ToBoolean($Policy.Password.ReversibleEncryption)
Confirm = $False
}
Set-ADDefaultDomainPasswordPolicy @SetADDefaultDomainPasswordPolicySplat
}
@@ -1,14 +0,0 @@
Account:
Lockout:
Threshold: 0
# Duration: '00:15:00.00'
# ObservationWindow: '00:05:00.00'
Password:
RequireComplexity: True
Age:
Minimum: 0
Maximum: 0
Length:
Minimum: 10
History: 0
ReversibleEncryption: False
+8
View File
@@ -0,0 +1,8 @@
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
netsh advfirewall firewall set rule group="Windows Remote Management" new enable=yes
$winrmService = Get-Service -Name WinRM
if ($winrmService.Status -eq "Running"){
Disable-PSRemoting -Force
}
Stop-Service winrm
Set-Service -Name winrm -StartupType Disabled
+18
View File
@@ -0,0 +1,18 @@
$NetworkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"))
$Connections = $NetworkListManager.GetNetworkConnections()
$Connections | ForEach-Object { $_.GetNetwork().SetCategory(1) }
Enable-PSRemoting -Force
winrm quickconfig -q
winrm quickconfig -transport:http
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="800"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'
winrm set winrm/config/listener?Address=*+Transport=HTTP '@{Port="5985"}'
netsh advfirewall firewall set rule group="Windows Remote Administration" new enable=yes
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=allow
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public new remoteip=any
Set-Service winrm -startuptype "auto"
Restart-Service winrm
+2
View File
@@ -0,0 +1,2 @@
@rem Silent mode, basic UI, no reboot
e:\setup64 /s /v "/qb REBOOT=R"
+73
View File
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8" ?>
<BlockList>
<!-- services to disable -->
<Services>
<Name>MVMCP2VAgent</Name>
<Name>VMTools</Name>
<Name> VMUpgradeHelper </Name>
<Name> vmvss </Name>
<Name>vmdesched</Name>
<Name>Virtual Server</Name>
<!-- Virtual Machine Helper -->
<Name>vmh</Name>
<!-- Xen-specific service -->
<Name>xensvc</Name>
</Services>
<!-- drivers to disable -->
<Drivers>
<Name>vmx_svga</Name>
<Name>vmmouse</Name>
<Name>vmscsi</Name>
<Name>amdpcn</Name>
<Name>PCnet</Name>
<Name>VMMEMCTL</Name>
<Name> pvscsi </Name>
<Name> vmci </Name>
<Name> vmmouse </Name>
<Name> vmaudio </Name>
<Name> vmrawdsk </Name>
<Name> vmxnet </Name>
<Name> vmxnet3ndis6 </Name>
<Name> vm3dmp </Name>
<Name> vmdebug </Name>
<Name> vmxnet3ndis5 </Name>
<Name>cirrus</Name>
<!-- storage drivers -->
<Name>buslogic</Name>
<Name>symc810</Name>
<Name>cpqarray</Name>
<Name>pcntn4m</Name>
<Name>cpqnf3</Name>
<Name>MRaidNT</Name>
<Name>Symc8XX</Name>
<!-- VIA chipset drivers -->
<Name>viaide</Name>
<Name>VIAudio</Name>
<Name>VIAPFD</Name>
<Name>viafilter</Name>
<Name>viaagp</Name>
<Name>viaagp1</Name>
<!-- network drivers: Intel(R) PRO/100 -->
<Name>E100B</Name>
<!-- tape drivers -->
<Name>4mmdat</Name>
<Name>4mmdat-SeSFT</Name>
<Name>SCSIChanger</Name>
<!-- Virtual Machine Monitor -->
<Name>vmm</Name>
<!-- Xen-specific drivers -->
<Name>xenevtchn</Name>
<Name>xenvbd</Name>
<Name>xennet</Name>
</Drivers>
<Programs>
<Name>ProMON</Name>
<Name>s3tray2</Name>
<Name>VMwareTray</Name>
<Name>VMwareUser</Name>
</Programs>
</BlockList>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16 -4
View File
@@ -16,7 +16,7 @@ $PowerCliConfigurationSplat = @{
Confirm = $False
InvalidCertificateAction = 'Ignore'
}
Set-PowerCLIConfiguration @PowerCliConfigurationSplat
Set-PowerCLIConfiguration @PowerCliConfigurationSplat | Out-Null
$ConnectVIServerSplat = @{
Server = $VSphereFQDN
@@ -26,14 +26,26 @@ $ConnectVIServerSplat = @{
}
Connect-VIServer @ConnectVIServerSplat | Out-Null
$GetVMSplat = @{
Name = "*$($VMName)*"
ErrorAction = 'SilentlyContinue'
}
If ([boolean](Get-VM @GetVMSplat)) {
$RemoveVMSplat = @{
VM = "$($VMName)*"
VM = Get-VM @GetVMSplat
DeletePermanently = $True
Confirm = $False
ErrorAction = 'SilentlyContinue'
}
Remove-VM @RemoveVMSplat
# Also delete ISO/floppy?
}
Disconnect-VIServer * -Confirm:$False
$RemoveItemSplat = @{
Path = "/scratch/*"
Recurse = $True
Force = $True
Confirm = $False
}
Remove-Item @RemoveItemSplat
+23
View File
@@ -0,0 +1,23 @@
# You cannot enable Windows PowerShell Remoting on network connections that are set to Public
# Spin through all the network locations and if they are set to Public, set them to Private
# using the INetwork interface:
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa370750(v=vs.85).aspx
# For more info, see:
# http://blogs.msdn.com/b/powershell/archive/2009/04/03/setting-network-location-to-private.aspx
# Network location feature was only introduced in Windows Vista - no need to bother with this
# if the operating system is older than Vista
if([environment]::OSVersion.version.Major -lt 6) { return }
# You cannot change the network location if you are joined to a domain, so abort
if(1,3,4,5 -contains (Get-WmiObject win32_computersystem).DomainRole) { return }
# Get network connections
$networkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"))
$connections = $networkListManager.GetNetworkConnections()
$connections |foreach {
Write-Host $_.GetNetwork().GetName()"category was previously set to"$_.GetNetwork().GetCategory()
$_.GetNetwork().SetCategory(1)
Write-Host $_.GetNetwork().GetName()"changed to category"$_.GetNetwork().GetCategory()
}
+41 -128
View File
@@ -1,21 +1,17 @@
DeploymentConfigurations:
- Id: primary
Label: Primary (redundant deployment)
Description: Initial Domain Controller with 'PDC Emulator'-role
- Id: secondary
Label: Secondary (redundant deployment)
Description: Additional Domain Controller
- Id: domainmember
Label: Domain member
Description: Windows 10 client joined to an Active Directory domain
- Id: standalone
Label: Stand-alone (non-redundant deployment)
Description: Single Domain Controller
Label: Stand-alone
Description: Stand-alone Windows 10 client
PropertyCategories:
- Name: ''
- Name: 0) Deployment information
ProductProperties:
- Key: deployment.type
Type: string
Value:
- primary
- secondary
- domainmember
- standalone
UserConfigurable: false
- Name: 1) Operating System
@@ -27,6 +23,22 @@ PropertyCategories:
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.administratorpw
Type: password(7..)
Label: Local administrator password*
Description: Must meet password complexity rules
DefaultValue: password
Configurations:
- standalone
UserConfigurable: true
- Key: guestinfo.ntpserver
Type: string(1..)
Label: Time server*
Description: A comma-separated list of timeservers
DefaultValue: 0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org
Configurations:
- standalone
UserConfigurable: true
- Name: 2) Networking
ProductProperties:
- Key: guestinfo.ipaddress
@@ -46,10 +58,9 @@ PropertyCategories:
- Key: guestinfo.dnsserver
Type: ip
Label: DNS server*
Description: Specify IP address of existing primary Domain Controller
DefaultValue: '127.0.0.1'
Configurations:
- secondary
Description: ''
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: guestinfo.gateway
Type: ip
@@ -58,129 +69,31 @@ PropertyCategories:
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Name: 3) Active Directory Domain Services
- Name: 3) Active Directory membership
ProductProperties:
- Key: addsconfig.domainname
Type: string(5..)
Type: string(1..)
Label: Domain name*
Description: 'Must be a valid FQDN'
DefaultValue: ''
Configurations: '*'
Description: Must be able to be resolved through provided DNS server
DefaultValue: example.org
Configurations:
- domainmember
UserConfigurable: true
- Key: addsconfig.netbiosname
Type: string(1..15)
Label: Domain short name (NetBIOS)*
Description: '(max length: 15 characters)'
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: addsconfig.administratorpw
Type: password(7..)
Label: Domain Administrator password*
Description: Must meet password complexity rules
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: addsconfig.safemodepw
Type: password(7..)
Label: Safe-mode password*
Description: Must meet password complexity rules
DefaultValue: ''
Configurations: '*'
UserConfigurable: true
- Key: addsconfig.ntpserver
- Key: addsconfig.username
Type: string(1..)
Label: Time server*
Description: A comma-separated list of upstream timeservers
DefaultValue: 0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org
Configurations:
- primary
- standalone
UserConfigurable: true
- Name: 4) Credential Management
ProductProperties:
- Key: vault.api
Type: string
Label: Vault API address
Description: The uri on which a HashiCorp Vault REST API can be reached
DefaultValue: ''
Configurations:
- primary
- standalone
UserConfigurable: true
- Key: vault.token
Type: password
Label: Vault API token
Description: An access token which has permissions to read/write to the Vault secrets engine
DefaultValue: ''
Configurations:
- primary
- standalone
UserConfigurable: true
- Key: vault.pwpolicy
Type: string
Label: Vault password policy
Description: A Vault password policy which determines complexity rules for generated passwords
DefaultValue: ''
Configurations:
- primary
- standalone
UserConfigurable: true
- Key: vault.secret
Type: string
Label: Vault secret name
Description: The name of the secret that all generated passwords will be stored in (as key/value pairs)
DefaultValue: ''
Configurations:
- primary
- standalone
UserConfigurable: true
- Name: 5) DHCP default scope
ProductProperties:
- Key: dhcpconfig.startip
Type: ip
Label: Start IP address
Label: Domain account username*
Description: ''
DefaultValue: '0.0.0.0'
DefaultValue: username
Configurations:
- secondary
- standalone
- domainmember
UserConfigurable: true
- Key: dhcpconfig.endip
Type: ip
Label: End IP address
- Key: addsconfig.password
Type: password(1..)
Label: Domain account password*
Description: ''
DefaultValue: '0.0.0.0'
DefaultValue: password
Configurations:
- secondary
- standalone
UserConfigurable: true
- Key: dhcpconfig.subnetmask
Type: ip
Label: Subnet mask
Description: ''
DefaultValue: '255.255.255.0'
Configurations:
- secondary
- standalone
UserConfigurable: true
- Key: dhcpconfig.gateway
Type: ip
Label: Gateway IP address
Description: ''
DefaultValue: '0.0.0.0'
Configurations:
- secondary
- standalone
UserConfigurable: true
- Key: dhcpconfig.leaseduration
Type: string(1..)
Label: Lease duration
Description: 'Enter as timestamp format (DD.HH:MM:SS.FFFF), or as a number of seconds'
DefaultValue: '01:00:00.00'
Configurations:
- secondary
- standalone
- domainmember
UserConfigurable: true
AdvancedOptions:
- Key: appliance.name
@@ -1,4 +1,3 @@
#Requires -Modules 'ADDSDeployment'
[CmdletBinding()]
Param(
# No parameters
@@ -36,14 +35,11 @@ foreach ($ovfProperty in $ovfProperties) {
# Check for mandatory values
Switch ($ovfPropertyValues['deployment.type']) {
'primary' {
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.netbiosname', 'addsconfig.administratorpw', 'addsconfig.safemodepw', 'addsconfig.ntpserver'), @()
}
'secondary' {
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.dnsserver', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.netbiosname', 'addsconfig.administratorpw', 'addsconfig.safemodepw', 'dhcpconfig.startip', 'dhcpconfig.endip', 'dhcpconfig.subnetmask', 'dhcpconfig.gateway', 'dhcpconfig.leaseduration'), @()
'domainmember' {
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.username', 'addsconfig.password'), @()
}
'standalone' {
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.netbiosname', 'addsconfig.administratorpw', 'addsconfig.safemodepw', 'addsconfig.ntpserver', 'dhcpconfig.startip', 'dhcpconfig.endip', 'dhcpconfig.subnetmask', 'dhcpconfig.gateway', 'dhcpconfig.leaseduration'), @()
$MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'guestinfo.administratorpw', 'guestinfo.ntpserver'), @()
}
default {
# Mandatory values missing, cannot provision.
@@ -156,12 +152,33 @@ If ((Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration').IPAddress -NotCon
$ErrorActionPreference, $OldErrorActionPreference = $OldErrorActionPreference, $NULL
}
# Promote to Domain Controller
If ((4,5) -NotContains (Get-WmiObject -Class 'Win32_ComputerSystem').DomainRole) {
Switch ($ovfPropertyValues['deployment.type']) {
'domainmember' {
# Join Active Directory domain as member
If (!(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain) {
$AddComputerSplat = @{
DomainName = $ovfPropertyValues['addsconfig.domainname']
Credential = New-Object System.Management.Automation.PSCredential(
$ovfPropertyValues['addsconfig.username'],
(ConvertTo-SecureString $ovfPropertyValues['addsconfig.password'] -AsPlainText -Force)
)
# OUPath = $ovfPropertyValues['addsconfig.organizationalunit']
Restart = $True
Force = $True
Confirm = $False
}
Add-Computer @AddComputerSplat
# Previous cmdlet performs a reboot on completion; so these are commented out
# Restart-Computer -Force
# Exit
}
}
'standalone' {
# Change password of built-in Administrator
$BuiltinAdministrator = (Get-LocalUser | Where-Object {$_.SID -match '-500'})
$ConvertToSecureStringSplat = @{
String = $ovfPropertyValues['addsconfig.administratorpw']
String = $ovfPropertyValues['guestinfo.administratorpw']
AsPlainText = $True
Force = $True
}
@@ -176,86 +193,13 @@ If ((4,5) -NotContains (Get-WmiObject -Class 'Win32_ComputerSystem').DomainRole)
}
Set-LocalUser @SetLocalUserSplat
$ResolveDNSNameSplat = @{
Name = "_ldap._tcp.dc._msdcs.$($ovfPropertyValues['addsconfig.domainname'])"
ErrorAction = 'SilentlyContinue'
}
$DNSRecord = Resolve-DnsName @ResolveDNSNameSplat
If ([boolean]$DNSRecord.PrimaryServer -eq $False) {
# No Primary Domain Controller found, installing as primary
$InstallADDSForestSplat = @{
DomainName = $ovfPropertyValues['addsconfig.domainname']
DomainNetbiosName = $ovfPropertyValues['addsconfig.netbiosname']
SafeModeAdministratorPassword = ConvertTo-SecureString $ovfPropertyValues['addsconfig.safemodepw'] -AsPlainText -Force
InstallDns = $True
DomainMode = 'WinThreshold'
ForestMode = 'WinThreshold'
$EnableLocalUserSplat = @{
InputObject = $BuiltinAdministrator
Confirm = $False
Force = $True
ErrorAction = 'Stop'
}
Try {
Install-ADDSForest @InstallADDSForestSplat
# Previous cmdlet performs a reboot on completion; so these are commented out
# Restart-Computer -Force
# Exit
}
Catch {
& schtasks.exe /Change /TN 'FirstBoot' /DISABLE
Stop-Computer -Force
Exit
Enable-LocalUser @EnableLocalUserSplat
}
}
Else {
# Primary Domain Controller is present, installing as secondary
$InstallADDSDomainControllerSplat = @{
DomainName = $ovfPropertyValues['addsconfig.domainname']
Credential = New-Object System.Management.Automation.PSCredential("$($ovfPropertyValues['addsconfig.netbiosname'])\$($BuiltinAdministrator.Name)", (ConvertTo-SecureString @ConvertToSecureStringSplat))
SafeModeAdministratorPassword = ConvertTo-SecureString $ovfPropertyValues['addsconfig.safemodepw'] -AsPlainText -Force
InstallDns = $True
Confirm = $False
Force = $True
ErrorAction = 'Stop'
}
Try {
Install-ADDSDomainController @InstallADDSDomainControllerSplat
# Previous cmdlet performs a reboot on completion; so these are commented out
# Restart-Computer -Force
# Exit
}
Catch {
& schtasks.exe /Change /TN 'FirstBoot' /DISABLE
Stop-Computer -Force
Exit
}
}
}
# Wait for Active Directory to become available
$Timestamp, $TimeoutMinutes = (Get-Date), 15
Do {
If ($Timestamp.AddMinutes($TimeoutMinutes) -lt (Get-Date)) {
$WriteEventLogSplat = @{
LogName = 'Application'
Source = 'FirstBoot'
EntryType = 'Warning'
EventID = 13
Message = "Timeout after $($TimeoutMinutes) minutes waiting for Active Directory to become available."
}
Write-EventLog @WriteEventLogSplat
Break
}
Start-Sleep -Seconds 30
$GetADComputerSplat = @{
Identity = $Env:ComputerName
ErrorAction = 'SilentlyContinue'
}
Get-ADComputer @GetADComputerSplat | Out-Null
} Until ($?)
# Iterate through and invoke all payload scripts
#! TODO: add registry values to determine which scripts have already been invoked (in case of intermediate reboots)