This script was created to to perform automated installations of gMSA (Group Managed Service Accounts) on servers that are allowed to use such accounts. A gMSA is protected so that only members of a specific domain group or assigned computer accounts can install the account and retrieve the password. You define such restrictions during the creation of the gMSA and there are many examples on the internet that outline the process.
There is no script output on screen unless an error has been detected and information on the installed accounts can be viewed in the Application eventlog of the system on which this is executed.
Managed Service Accounts have been introduced with Windows Server 2008 R2 however such accounts could only be used on a single computer. That problem has been solved with the introduction of gMSAs but keep in mind that gMSAs can only be used if your Active Directory forest is at least on the level of Windows Server 2012 and so are the domain members on which you want to use these accounts. More information can be found on Microsoft Technet.
It is advised to use this script on Windows Server 2016 or higher due to the fact that the ActiveDirectory PowerShell module is required which is installed automatically through the script and default possible on Windows Server 2016 or higher. Windows Server 2016 is also the first Operating System that has the container feature available which is used for Docker.
You can verify if a gMSA can be installed on the system by running Test-ADServiceAccount to determine if the account can be installed and used. A warning message will be thrown if the account cannot be used, this has been suppressed within the script to prevent messages and is seen as an indication that the account cannot be used by the system that performs the check.
Example(s):
In the below example(s) certain information is removed due to the fact that this is gathered from a live environment.
Code:
<#
.Synopsis
Cycles through all Group Managed Service accounts in the domain and installs the applicable accounts or the local system.
.Description
By default manual actions are required to test and install a gMSA (Group Managed Service Account) on the local system however that prevents auto deployments which are required.
This script will cycle through all the gMSAs in the domain and performs a test on each account, the account that is tested successfully can be installed on the local system.
If Docker is detected a local credential file is created for use with containers.
The New-CredentialSpec and Get-CredentialSpec functions are pulled from the following link:
https://raw.githubusercontent.com/Microsoft/Virtualization-Documentation/live/windows-server-container-tools/ServiceAccounts/CredentialSpec.psm1
However these contain a bug that does not work in our test case if the Docker root directory is placed in a different location other than default which is solved within this script.
#>
Function New-CredentialSpec {
<#
.Synopsis
Creates and stores a credential specification file
.Description
Windows containers are able to run with an Active Directory identity. This enables applications running
in the container to use Windows authentication instead of stored username/password combinations.
Each credential spec contains:
- A default account used that will be mapped to LocalSystem & Network Service in the container
- (Optional) Additional group Managed Service Accounts that may be used in the container
.Parameter Name
The name for the new credential specification
.Parameter AccountName
The group Managed Service Account name used for the default account
.Parameter Domain
The Active Directory domain used for the default account. If not specified, will use the domain of the host.
.Parameter AdditionalAccounts
A list of additional group Managed Service Accounts that will be available to running services
.Example
# Create a new credential spec named "ContainerApp1"
New-CredentialSpec -Name "ContainerApp1" -Domain (Get-ADDomain) -AccountName "AppAccount1"
.Example
# Create a new credential spec named "CS1"
# - with default account "WebApp1"
# - and an additional account "acct1" on domain "domain1"
New-CredentialSpec -Name CS1 -AccountName WebApp1 -AdditionalAccounts @{DomainName = "domain1"; AccountName = "acct1" }, @{DomainName = "domain1"; AccountName="acct2"}
#>
param(
[Parameter(Mandatory = $true)] [String] $Name,
[Parameter(Mandatory = $true)] [String] $AccountName,
[Parameter(Mandatory = $false)] [Microsoft.ActiveDirectory.Management.ADDomain] $Domain = (Get-ADDomain),
[Parameter(Mandatory = $false)] $AdditionalAccounts
)
# Start hash table for output
$output = @{ }
# Create ActiveDirectoryConfig Object
$output.ActiveDirectoryConfig = @{ }
$output.ActiveDirectoryConfig.GroupManagedServiceAccounts = @( @{"Name" = $AccountName; "Scope" = $Domain.DNSRoot } )
$output.ActiveDirectoryConfig.GroupManagedServiceAccounts += @{"Name" = $AccountName; "Scope" = $Domain.NetBIOSName }
if ($AdditionalAccounts) {
$AdditionalAccounts | ForEach-Object {
$output.ActiveDirectoryConfig.GroupManagedServiceAccounts += @{"Name" = $_.AccountName; "Scope" = $_.DomainName }
}
}
# Create CmsPlugins Object
$output.CmsPlugins = @("ActiveDirectory")
# Create DomainJoinConfig Object
$output.DomainJoinConfig = @{ }
$output.DomainJoinConfig.DnsName = $Domain.DNSRoot
$output.DomainJoinConfig.Guid = $Domain.ObjectGUID
$output.DomainJoinConfig.DnsTreeName = $Domain.Forest
$output.DomainJoinConfig.NetBiosName = $Domain.NetBIOSName
$output.DomainJoinConfig.Sid = $Domain.DomainSID.Value
$output.DomainJoinConfig.MachineAccountName = $AccountName
$output | ConvertTo-Json -Depth 5 | Out-File -FilePath "$($Script:CredentialSpecPath)\\$($Name).json" -encoding ascii
}
Function Get-CredentialSpec {
<#
.Synopsis
Gets all credential specs on current system
.Description
Windows containers are able to run with an Active Directory identity. This enables applications running
in the container to use Windows authentication instead of stored username/password combinations.
#>
Get-ChildItem $Script:CredentialSpecPath | Select-Object @{
Name = 'Name'
Expression = { $_.BaseName }
},
@{
Name = 'Path'
Expression = { $_.FullName }
}
}
If ((Get-WindowsFeature -Name RSAT-AD-PowerShell).Installed -eq $False) {
Install-WindowsFeature -Name RSAT-AD-PowerShell
}
Import-Module ActiveDirectory
Try {
$EventLogSourceName = "gMSAInstaller"
If ([System.Diagnostics.EventLog]::SourceExists($EventLogSourceName) -eq $false) {
[System.Diagnostics.EventLog]::CreateEventSource($($EventLogSourceName), "Application")
}
$AllgMSAs = Get-ADServiceAccount -Filter *
ForEach ($gMSA in $AllgMSAs) {
If ((Test-ADServiceAccount $($gMSA.Name) -WarningAction SilentlyContinue) -eq $True) {
Install-ADServiceAccount -Identity $($gMSA.Name)
Write-Eventlog -logname Application -source $EventLogSourceName -eventID 3009 -entrytype Information -message "Installed gMSA with name $($gMSA.Name)" -category 1 -rawdata 10, 20
If ((Get-Service -Name Docker -ErrorAction SilentlyContinue).Status -eq "running") {
$DockerCredentialSpecPath = "$($(docker info --format '{{json .DockerRootDir}}').Replace('"',''))\CredentialSpecs"
$DockerCredentialSpecPath = $DockerCredentialSpecPath.Replace("\\", "\")
If (Test-Path $DockerCredentialSpecPath) {
$Script:CredentialSpecPath = $DockerCredentialSpecPath
Try {
New-CredentialSpec -Name $($gMSA.Name) -AccountName $($gMSA.Name)
Write-Eventlog -logname Application -source $EventLogSourceName -eventID 3009 -entrytype Information -message "Created CredentialSpec file for gMSA with name $($gMSA.Name) in file $($DockerCredentialSpecPath)\$($gMSA.Name).json" -category 1 -rawdata 10, 20
}
Catch {
Write-Warning "Error occured during creation of CredentialSpec for gMSA!"
Write-Host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
Write-Host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
Write-Eventlog -logname Application -source $EventLogSourceName -eventID 3009 -entrytype Error -message "Error occured during creation of CredentialSpec for gMSA with name $($gMSA.Name) in location $($DockerCredentialSpecPath):`n`nException Type: $($_.Exception.GetType().FullName)`nException Message: $($_.Exception.Message)" -category 1 -rawdata 10, 20
}
}
}
}
}
}
Catch {
Write-Warning "Error occured during installation of gMSAs!"
Write-Host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
Write-Host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
Write-Eventlog -logname Application -source $EventLogSourceName -eventID 3009 -entrytype Error -message "Error occured during installation of gMSA with name $($gMSA.Name):`n`nException Type: $($_.Exception.GetType().FullName)`nException Message: $($_.Exception.Message)" -category 1 -rawdata 10, 20
}