Created
April 8, 2016 23:10
-
-
Save JohnLBevan/8058b2da9bbe1f3e1b1dafa7dad1e204 to your computer and use it in GitHub Desktop.
This is a script to aid in determining issues with the SPN configuration of AX.
Aids in gathering and amending the info discussed here: https://blogs.msdn.microsoft.com/axsupport/2015/03/20/enhanced-security-with-kerberos-only-authentication-in-microsoft-dynamics-ax/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
clear-host | |
#this script is based on info here: https://blogs.msdn.microsoft.com/axsupport/2015/03/20/enhanced-security-with-kerberos-only-authentication-in-microsoft-dynamics-ax/ | |
#would you like to take advantage of the script's more advanced functions? | |
[boolean]$Compare = $true | |
[boolean]$update = $false | |
# set this to an string array of ax server names; the sample code below shows a quick way if machines follow numeric based naming conventions | |
[string[]]$AxMachineNames = ` | |
([string[]](1..5 | %{("MyAxAosServer{0:00}" -f $_)})) + | |
([string[]](1..10 | %{("MyTerminalServer{0:00}AxClient" -f $_)})) | |
#$AxMachineNames #uncomment this line to see a list of the server names generated by the above shorthand | |
#region local functions & setup | |
function Create-AxRegistryKeyInfo { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)] | |
[string]$KeyName | |
, | |
[Parameter(Mandatory=$true)] | |
[ValidateSet('Client','Server',('Client','Server'))] #added last option so ISE intellisense offers both (missing a comma, but still less typing) | |
[string[]]$AppliesToType #= ('Client','Server') #defaulting doesn't play with my PSBoundParameters approach | |
, | |
[Parameter(Mandatory=$true)] | |
[Microsoft.Win32.RegistryValueKind]$PropertyType | |
, | |
[Parameter(Mandatory=$false)] | |
[AllowEmptyString()] | |
[string]$DesiredValue #specifying this allows the code to compare config with desired rather than just returning current | |
, | |
[Parameter(Mandatory=$false)] | |
[string]$Notes #included to aid this code in being self documenting | |
) | |
process { | |
[PSCustomObject][hashtable]$PSBoundParameters | |
} | |
} | |
[PSCustomObject[]]$axKeysOfInterest = @( | |
Create-AxRegistryKeyInfo -KeyName 'authn_service' -PropertyType String -AppliesToType Client, Server -DesiredValue 'Default' -Notes @" | |
value: | |
- Default (NTLM) | |
- 9 (Negotiate) | |
- 16 (Kerberos) | |
can also be defined in config (AXC); e.g. for kerberos: authn_service,text,16 | |
"@ | |
Create-AxRegistryKeyInfo -KeyName 'authn_regspn' -PropertyType String -AppliesToType Server -DesiredValue '0' -Notes @" | |
value: | |
- 0 (do not register spn) | |
- 1 (register spn; this is the default) | |
"@ | |
Create-AxRegistryKeyInfo -KeyName 'authn_fqdn' -PropertyType String -AppliesToType Client -Notes @" | |
value: | |
- 0 (do append domain suffix) | |
- 1 (append domain suffix; this is the default value) | |
- <string> (i.e. a domain suffix (ex. mydomain.corp.com)) | |
note: if there is no domain suffix supplied by the client config / ax load balance, the domain of the client machine is used. | |
"@ | |
Create-AxRegistryKeyInfo -KeyName 'authn_usehost' -PropertyType String -AppliesToType Client, Server -Notes @" | |
value: | |
- 0 (do not use host format; this is the default) | |
- 1 (use host format) | |
(I don't know what this setting means by "host format" :S) | |
"@ | |
Create-AxRegistryKeyInfo -KeyName 'debugrpc' -PropertyType String -AppliesToType Client, Server -DesiredValue '0' -Notes @" | |
value: | |
- 0 (disables event log debug logging) | |
- 1 (enables event log debugging) | |
Logs additional information regarding SPN registration, authentication mode, and SPN value. | |
"@ | |
) | |
#endregion local functions & setup | |
#region script block for server | |
[scriptblock]$CodeForRemoteMachine = { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)] | |
[ValidateSet('Client','Server')] | |
[string[]]$Types | |
, | |
[Parameter(Mandatory=$false)] #if not supplied we still get useful info on version and config; some may want to use that way? | |
[PSCustomObject[]]$Keys | |
, | |
[Parameter(Mandatory=$false)] | |
[boolean]$Compare = $false | |
, | |
[Parameter(Mandatory=$false)] | |
[boolean]$Update = $false | |
) | |
#region helper functions | |
#http://stackoverflow.com/questions/25682507/powershell-inline-if-iif | |
Function IIf($If, $IfTrue, $IfFalse) { | |
if ($If) { | |
if ($IfTrue -is 'ScriptBlock') {&$IfTrue} else {$IfTrue} | |
} else { | |
if ($IfFalse -is 'ScriptBlock') {&$IfFalse} else {$IfFalse} | |
} | |
} | |
#endregion | |
function Process-Key { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true, ValueFromPipeline = $true)] | |
[PSCustomObject]$KeyInfo | |
, | |
[Parameter(Mandatory=$true)] | |
[Microsoft.Win32.RegistryKey]$RegistryKey | |
, | |
[Parameter(Mandatory=$true)] | |
[PSCustomObject]$ResultObject #this is updated as a result of this call | |
, | |
[Parameter(Mandatory=$false)] | |
[string]$NotFound = 'Property Does Not Exist!' | |
, | |
[Parameter(Mandatory=$false)] | |
[switch]$Compare | |
, | |
[Parameter(Mandatory=$false)] | |
[switch]$Update #not yet coded | |
) | |
process { | |
[string]$Value = $RegistryKey.GetValue($KeyInfo.KeyName, $NotFound) | |
$ResultObject | Add-Member -MemberType NoteProperty -Name "Key:$($KeyInfo.KeyName)" -Value $value | |
if($Compare.IsPresent) { | |
if ($KeyInfo.DesiredValue -ne $null) { | |
if ($KeyInfo.DesiredValue -eq $value) { | |
$ResultObject.MatchesDesiredValue = $ResultObject.MatchesDesiredValue + $KeyInfo.KeyName | |
} else { | |
$ResultObject.DoesNotMatchDesiredValue = $ResultObject.DoesNotMatchDesiredValue + $KeyInfo.KeyName | |
} | |
} else { | |
#write-warning $KeyInfo.KeyName | |
$ResultObject.NoDesiredValueSpecified = $ResultObject.NoDesiredValueSpecified + $KeyInfo.KeyName | |
} | |
} | |
if($Update.IsPresent) { | |
if (($KeyInfo.DesiredValue -ne $null) -and ($KeyInfo.DesiredValue -ne $value)) { | |
New-ItemProperty -Path $RegistryKey.PSPath -Name $KeyInfo.KeyName -Value $KeyInfo.DesiredValue -PropertyType $KeyInfo.PropertyType -Force | Out-Null | |
} | |
} | |
} | |
} | |
function Get-AxSpnConfigInfo { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true, ValueFromPipeline = $true)] | |
[ValidateSet('Client','Server')] | |
[string]$Type | |
, | |
[Parameter(Mandatory=$false)] #if not supplied we still get useful info on version and config; some may want to use that way? | |
[PSCustomObject[]]$Keys | |
, | |
[Parameter(Mandatory=$false)] | |
[switch]$Compare | |
, | |
[Parameter(Mandatory=$false)] | |
[switch]$Update | |
) | |
begin { | |
[string]$serverRoot = 'HKLM:\SYSTEM\CurrentControlSet\services\Dynamics Server\' | |
[string]$clientRoot = 'HKLM:\SOFTWARE\Microsoft\Dynamics\' | |
} | |
process { | |
get-childitem (IIf ($type -eq 'Client') $clientRoot $serverRoot) -ErrorAction SilentlyContinue | ?{$_.PSChildName -match '\d\.\d'} | %{ | |
[string]$version = $_.PSChildName | |
$_ | get-childitem | ?{($type -ne 'Client') -or ($_.PSChildName -eq 'configuration')} | %{ | |
#clients don't have instances; but they do have an additional path, 'configuration' | |
[string]$instance = $_.PSChildName | |
$_ | get-childitem | %{ | |
[string]$configuration = $_.PSChildName | |
[Microsoft.Win32.RegistryKey]$key = $_ #| get-item | |
[PSCustomObject]$Result = [PSCustomObject]@{ | |
#ComputerName = $env:COMPUTERNAME #this is returned by invoke command anyway; just exclude the -HideComputerName parameter | |
Type = $type | |
Version = $version | |
Instance = $instance | |
Configuration = $configuration | |
} | |
if($Compare.IsPresent) { | |
$Result | Add-Member -MemberType NoteProperty -Name 'MatchesDesiredValue' -Value ([string[]]@()) | |
$Result | Add-Member -MemberType NoteProperty -Name 'DoesNotMatchDesiredValue' -Value ([string[]]@()) | |
$Result | Add-Member -MemberType NoteProperty -Name 'NoDesiredValueSpecified' -Value ([string[]]@()) | |
} | |
$Keys | ?{$_.AppliesToType -contains $type} | Process-Key -RegistryKey $Key -ResultObject $Result -Compare:$Compare -Update:$Update | |
$Key.Close() | |
$Result | |
} | |
} | |
} | |
} | |
} | |
$Types | Get-AxSpnConfigInfo -Keys $Keys -Compare:$Compare -Update:$Update | |
} | |
#endregion script block for server | |
[PSCustomObject[]]$Results = invoke-command -ComputerName $AxMachineNames -ScriptBlock $CodeForRemoteMachine -ArgumentList ('Client','Server'),$axKeysOfInterest,$Compare,$Update | select @{N='ComputerName';E={$_.PSComputerName}}, * -ExcludeProperty RunspaceId, PSComputerName | |
#now we can do what we like with this info; for now just doing a simple listing of the results | |
$Results | sort Type, ComputerName, Version, Instance, Configuration | ft Type, ComputerName, Instance -AutoSize |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment