Skip to content

Instantly share code, notes, and snippets.

@JohnLBevan
Created April 8, 2016 23:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JohnLBevan/8058b2da9bbe1f3e1b1dafa7dad1e204 to your computer and use it in GitHub Desktop.
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/
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