Skip to content

Commit 73830aa

Browse files
Improve SAMR connectivity
1 parent ed13725 commit 73830aa

10 files changed

Lines changed: 102 additions & 34 deletions

File tree

Documentation/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ All notable changes to this project will be documented in this file. The format
77

88
## [Unreleased]
99

10-
- Nothing yet.
10+
### Added
11+
12+
- Added the `-UseNamedPipe` parameter to the `Get-SamPasswordPolicy` cmdlet.
13+
14+
### Fixed
15+
16+
- Fixed a missing `throw` in `SafeUnicodeSecureStringPointer` that silently ignored invalid password byte arrays.
17+
- Improved SAMR authentication fallback for localhost and missing SPN scenarios.
1118

1219
## [6.4] - 2026-03-28
1320

Documentation/PowerShell/Add-ADReplSidHistory.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Specifies the destination domain in which the destination principal resides. The
112112
```yaml
113113
Type: String
114114
Parameter Sets: CrossForest
115-
Aliases:
115+
Aliases: DstDomain
116116

117117
Required: True
118118
Position: Named
@@ -127,7 +127,7 @@ Specifies the destination security principal that receives the source SID histor
127127
```yaml
128128
Type: String
129129
Parameter Sets: CrossForest, IntraDomain
130-
Aliases:
130+
Aliases: DstPrincipal
131131

132132
Required: True
133133
Position: Named
@@ -157,7 +157,7 @@ Specifies the credentials to be used in the source domain.
157157
```yaml
158158
Type: PSCredential
159159
Parameter Sets: CrossForest
160-
Aliases:
160+
Aliases: SrcCreds
161161

162162
Required: False
163163
Position: Named
@@ -172,7 +172,7 @@ Specifies the source domain to query for the SID of the source principal. The do
172172
```yaml
173173
Type: String
174174
Parameter Sets: CrossForest
175-
Aliases:
175+
Aliases: SrcDomain
176176

177177
Required: True
178178
Position: Named
@@ -187,7 +187,7 @@ Specifies the primary domain controller (PDC) or PDC role owner in the source do
187187
```yaml
188188
Type: String
189189
Parameter Sets: CrossForest
190-
Aliases:
190+
Aliases: SrcDomainController, SourceServer
191191

192192
Required: False
193193
Position: Named
@@ -202,7 +202,7 @@ Specifies the source security principal whose SID history is to be added. If -De
202202
```yaml
203203
Type: String
204204
Parameter Sets: CrossForest, IntraDomain
205-
Aliases:
205+
Aliases: SrcPrincipal
206206

207207
Required: True
208208
Position: Named

Documentation/PowerShell/Get-SamPasswordPolicy.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Queries Active Directory for the default password policy.
1313
## SYNTAX
1414

1515
```
16-
Get-SamPasswordPolicy -Domain <String> [-Credential <PSCredential>] [-Server <String>] [<CommonParameters>]
16+
Get-SamPasswordPolicy -Domain <String> [-UseNamedPipe] [-Credential <PSCredential>] [-Server <String>]
17+
[<CommonParameters>]
1718
```
1819

1920
## DESCRIPTION
@@ -102,6 +103,21 @@ Accept pipeline input: False
102103
Accept wildcard characters: False
103104
```
104105
106+
### -UseNamedPipe
107+
Use named pipes (RPC/NP) transport instead of TCP to connect to the target server.
108+
109+
```yaml
110+
Type: SwitchParameter
111+
Parameter Sets: (All)
112+
Aliases: UseNamedPipes
113+
114+
Required: False
115+
Position: Named
116+
Default value: None
117+
Accept pipeline input: False
118+
Accept wildcard characters: False
119+
```
120+
105121
### CommonParameters
106122
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
107123

Src/DSInternals.Common/Interop/SafeUnicodeSecureStringPointer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed class SafeUnicodeSecureStringPointer : SafeHandleZeroOrMinusOneIsI
1212
{
1313
private int numChars;
1414

15-
public SafeUnicodeSecureStringPointer(SecureString password)
15+
public SafeUnicodeSecureStringPointer(SecureString? password)
1616
: base(true)
1717
{
1818
if (password != null)
@@ -23,15 +23,15 @@ public SafeUnicodeSecureStringPointer(SecureString password)
2323
}
2424
}
2525

26-
public SafeUnicodeSecureStringPointer(byte[] password)
26+
public SafeUnicodeSecureStringPointer(byte[]? password)
2727
: base(true)
2828
{
2929
if (password != null)
3030
{
3131
if (password.Length % sizeof(char) == 1)
3232
{
3333
// Unicode strings must have even number of bytes
34-
new ArgumentOutOfRangeException(nameof(password));
34+
throw new ArgumentOutOfRangeException(nameof(password));
3535
}
3636

3737
IntPtr buffer = Marshal.AllocHGlobal(password.Length + sizeof(char));

Src/DSInternals.Common/Interop/WindowsAuthenticationIdentity.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public struct WindowsAuthenticationIdentity : IDisposable
3535
/// <summary>
3636
/// String containing the user's password in the domain or workgroup.
3737
/// </summary>
38-
private SafeUnicodeSecureStringPointer? _password;
38+
private SafeUnicodeSecureStringPointer _password;
3939

4040
/// <summary>
4141
/// Number of characters in Password, excluding the terminating NULL.
@@ -85,7 +85,7 @@ public string? Domain
8585
/// <summary>
8686
/// User's password.
8787
/// </summary>
88-
public SecureString Password
88+
public SecureString? Password
8989
{
9090
set
9191
{
@@ -100,14 +100,19 @@ public SecureString Password
100100
/// <summary>
101101
/// Initializes a new instance of the <see cref="WindowsAuthenticationIdentity"/> struct.
102102
/// </summary>
103-
public WindowsAuthenticationIdentity(NetworkCredential credential)
103+
public WindowsAuthenticationIdentity(NetworkCredential? credential)
104104
{
105105
if (credential != null)
106106
{
107107
User = credential.UserName;
108108
Domain = credential.Domain;
109109
Password = credential.SecurePassword;
110110
}
111+
else
112+
{
113+
// Impersonate the current user with a null password
114+
Password = null;
115+
}
111116
}
112117

113118
/// <summary>

Src/DSInternals.PowerShell/Commands/Base/SamCommandBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ protected SamServer SamServer
1616
private set;
1717
}
1818

19+
// Most password-related operations are denied when RPC over TCP is used,
20+
// so we use RPC over named pipes by default.
21+
protected virtual bool UseNamedPipes => true;
22+
1923
#region Parameters
2024
[Parameter(
2125
HelpMessage = "Specify the user account credentials to use to perform this task. The default credentials are the credentials of the currently logged on user.",
@@ -61,7 +65,7 @@ protected override void BeginProcessing()
6165
try
6266
{
6367
NetworkCredential? netCred = this.Credential?.GetNetworkCredential();
64-
this.SamServer = new(this.Server, SamServerAccessMask.LookupDomain | SamServerAccessMask.EnumerateDomains, netCred, useNamedPipes: true);
68+
this.SamServer = new(this.Server, SamServerAccessMask.LookupDomain | SamServerAccessMask.EnumerateDomains, netCred, this.UseNamedPipes);
6569
}
6670
catch (Win32Exception ex)
6771
{

Src/DSInternals.PowerShell/Commands/LSA/GetSamPasswordPolicyCommand.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,20 @@ public string Domain
1616
get;
1717
set;
1818
}
19+
20+
[Parameter]
21+
[Alias("UseNamedPipes")]
22+
public SwitchParameter UseNamedPipe
23+
{
24+
get;
25+
set;
26+
}
1927
#endregion Parameters
2028

2129
#region Cmdlet Overrides
30+
31+
protected override bool UseNamedPipes => UseNamedPipe.IsPresent;
32+
2233
protected override void ProcessRecord()
2334
{
2435
this.WriteVerbose($"Opening domain {this.Domain}.");

Src/DSInternals.PowerShell/en-US/DSInternals.PowerShell.dll-Help.xml

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
10381038
</dev:type>
10391039
<dev:defaultValue>False</dev:defaultValue>
10401040
</command:parameter>
1041-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
1041+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="DstPrincipal">
10421042
<maml:name>DestinationPrincipal</maml:name>
10431043
<maml:description>
10441044
<maml:para>Specifies the destination security principal that receives the source SID history.</maml:para>
@@ -1062,7 +1062,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
10621062
</dev:type>
10631063
<dev:defaultValue>None</dev:defaultValue>
10641064
</command:parameter>
1065-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
1065+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="SrcPrincipal">
10661066
<maml:name>SourcePrincipal</maml:name>
10671067
<maml:description>
10681068
<maml:para>Specifies the source security principal whose SID history is to be added. If -DeleteSourceObject is specified, this value should be a DN; otherwise, it should be a domain-relative SAM name.</maml:para>
@@ -1089,7 +1089,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
10891089
</dev:type>
10901090
<dev:defaultValue>None</dev:defaultValue>
10911091
</command:parameter>
1092-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1092+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="DstDomain">
10931093
<maml:name>DestinationDomain</maml:name>
10941094
<maml:description>
10951095
<maml:para>Specifies the destination domain in which the destination principal resides. The domain name can be an FQDN or a NetBIOS name.</maml:para>
@@ -1101,7 +1101,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
11011101
</dev:type>
11021102
<dev:defaultValue>None</dev:defaultValue>
11031103
</command:parameter>
1104-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
1104+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="DstPrincipal">
11051105
<maml:name>DestinationPrincipal</maml:name>
11061106
<maml:description>
11071107
<maml:para>Specifies the destination security principal that receives the source SID history.</maml:para>
@@ -1125,7 +1125,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
11251125
</dev:type>
11261126
<dev:defaultValue>None</dev:defaultValue>
11271127
</command:parameter>
1128-
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1128+
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="SrcCreds">
11291129
<maml:name>SourceCredential</maml:name>
11301130
<maml:description>
11311131
<maml:para>Specifies the credentials to be used in the source domain.</maml:para>
@@ -1137,7 +1137,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
11371137
</dev:type>
11381138
<dev:defaultValue>None</dev:defaultValue>
11391139
</command:parameter>
1140-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1140+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="SrcDomain">
11411141
<maml:name>SourceDomain</maml:name>
11421142
<maml:description>
11431143
<maml:para>Specifies the source domain to query for the SID of the source principal. The domain name can be an FQDN or a NetBIOS name.</maml:para>
@@ -1149,7 +1149,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
11491149
</dev:type>
11501150
<dev:defaultValue>None</dev:defaultValue>
11511151
</command:parameter>
1152-
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1152+
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="SrcDomainController, SourceServer">
11531153
<maml:name>SourceDomainController</maml:name>
11541154
<maml:description>
11551155
<maml:para>Specifies the primary domain controller (PDC) or PDC role owner in the source domain.</maml:para>
@@ -1161,7 +1161,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
11611161
</dev:type>
11621162
<dev:defaultValue>None</dev:defaultValue>
11631163
</command:parameter>
1164-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
1164+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="SrcPrincipal">
11651165
<maml:name>SourcePrincipal</maml:name>
11661166
<maml:description>
11671167
<maml:para>Specifies the source security principal whose SID history is to be added. If -DeleteSourceObject is specified, this value should be a DN; otherwise, it should be a domain-relative SAM name.</maml:para>
@@ -1212,7 +1212,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
12121212
</dev:type>
12131213
<dev:defaultValue>False</dev:defaultValue>
12141214
</command:parameter>
1215-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1215+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="DstDomain">
12161216
<maml:name>DestinationDomain</maml:name>
12171217
<maml:description>
12181218
<maml:para>Specifies the destination domain in which the destination principal resides. The domain name can be an FQDN or a NetBIOS name.</maml:para>
@@ -1224,7 +1224,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
12241224
</dev:type>
12251225
<dev:defaultValue>None</dev:defaultValue>
12261226
</command:parameter>
1227-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
1227+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="DstPrincipal">
12281228
<maml:name>DestinationPrincipal</maml:name>
12291229
<maml:description>
12301230
<maml:para>Specifies the destination security principal that receives the source SID history.</maml:para>
@@ -1248,7 +1248,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
12481248
</dev:type>
12491249
<dev:defaultValue>None</dev:defaultValue>
12501250
</command:parameter>
1251-
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1251+
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="SrcCreds">
12521252
<maml:name>SourceCredential</maml:name>
12531253
<maml:description>
12541254
<maml:para>Specifies the credentials to be used in the source domain.</maml:para>
@@ -1260,7 +1260,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
12601260
</dev:type>
12611261
<dev:defaultValue>None</dev:defaultValue>
12621262
</command:parameter>
1263-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1263+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="SrcDomain">
12641264
<maml:name>SourceDomain</maml:name>
12651265
<maml:description>
12661266
<maml:para>Specifies the source domain to query for the SID of the source principal. The domain name can be an FQDN or a NetBIOS name.</maml:para>
@@ -1272,7 +1272,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
12721272
</dev:type>
12731273
<dev:defaultValue>None</dev:defaultValue>
12741274
</command:parameter>
1275-
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="none">
1275+
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="SrcDomainController, SourceServer">
12761276
<maml:name>SourceDomainController</maml:name>
12771277
<maml:description>
12781278
<maml:para>Specifies the primary domain controller (PDC) or PDC role owner in the source domain.</maml:para>
@@ -1284,7 +1284,7 @@ PS C:\&gt; Start-Service -Name ntds</dev:code>
12841284
</dev:type>
12851285
<dev:defaultValue>None</dev:defaultValue>
12861286
</command:parameter>
1287-
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="none">
1287+
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName)" position="named" aliases="SrcPrincipal">
12881288
<maml:name>SourcePrincipal</maml:name>
12891289
<maml:description>
12901290
<maml:para>Specifies the source security principal whose SID history is to be added. If -DeleteSourceObject is specified, this value should be a DN; otherwise, it should be a domain-relative SAM name.</maml:para>
@@ -9061,6 +9061,17 @@ Local Domain SID : S-1-5-21-2929860833-2984454239-2848460202
90619061
</dev:type>
90629062
<dev:defaultValue>localhost</dev:defaultValue>
90639063
</command:parameter>
9064+
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="UseNamedPipes">
9065+
<maml:name>UseNamedPipe</maml:name>
9066+
<maml:description>
9067+
<maml:para>Use named pipes (RPC/NP) transport instead of TCP to connect to the target server.</maml:para>
9068+
</maml:description>
9069+
<dev:type>
9070+
<maml:name>SwitchParameter</maml:name>
9071+
<maml:uri />
9072+
</dev:type>
9073+
<dev:defaultValue>False</dev:defaultValue>
9074+
</command:parameter>
90649075
</command:syntaxItem>
90659076
</command:syntax>
90669077
<command:parameters>
@@ -9101,6 +9112,18 @@ Local Domain SID : S-1-5-21-2929860833-2984454239-2848460202
91019112
</dev:type>
91029113
<dev:defaultValue>localhost</dev:defaultValue>
91039114
</command:parameter>
9115+
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="named" aliases="UseNamedPipes">
9116+
<maml:name>UseNamedPipe</maml:name>
9117+
<maml:description>
9118+
<maml:para>Use named pipes (RPC/NP) transport instead of TCP to connect to the target server.</maml:para>
9119+
</maml:description>
9120+
<command:parameterValue required="false" variableLength="false">SwitchParameter</command:parameterValue>
9121+
<dev:type>
9122+
<maml:name>SwitchParameter</maml:name>
9123+
<maml:uri />
9124+
</dev:type>
9125+
<dev:defaultValue>False</dev:defaultValue>
9126+
</command:parameter>
91049127
</command:parameters>
91059128
<command:inputTypes>
91069129
<command:inputType>

Src/DSInternals.SAM/Interop/NativeMethods.Sam.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@ internal static NtStatus SamConnect(string serverName, out SafeSamHandle serverH
4545
[DllImport(SamLib, SetLastError = true, CharSet = CharSet.Unicode)]
4646
private static extern NtStatus SamConnectWithCreds([In] ref UnicodeString serverName, out SafeSamHandle serverHandle, SamServerAccessMask accessMask, IntPtr objectAttributes, [In] ref WindowsAuthenticationIdentity authIdentity, [MarshalAs(UnmanagedType.LPWStr)] string? servicePrincipalName, [MarshalAs(UnmanagedType.Bool)] out bool isWin2k);
4747

48-
internal static NtStatus SamConnectWithCreds(string serverName, out SafeSamHandle serverHandle, SamServerAccessMask accessMask, NetworkCredential credential)
48+
internal static NtStatus SamConnectWithCreds(string serverName, out SafeSamHandle serverHandle, SamServerAccessMask accessMask, NetworkCredential? credential)
4949
{
5050
UnicodeString unicodeServerName = new(serverName);
5151
string servicePrincipalName = SpnPrefix + serverName;
5252
WindowsAuthenticationIdentity authIdentity = new(credential);
5353

5454
NtStatus result = SamConnectWithCreds(ref unicodeServerName, out serverHandle, accessMask, objectAttributes: IntPtr.Zero, ref authIdentity, servicePrincipalName, out bool _);
5555

56-
if (result == NtStatus.RpcUnknownAuthenticationService)
56+
// If the server does not support Kerberos or if the SPN is not found, the server may return STATUS_RPC_UNKNOWN_AUTHENTICATION_SERVICE or STATUS_INVALID_HANDLE.
57+
// STATUS_INVALID_HANDLE is also returned for localhost connections.
58+
if (result == NtStatus.RpcUnknownAuthenticationService || result == NtStatus.InvalidHandle)
5759
{
5860
// Try it again, but without the SPN, to force NTLM instead of Negotiate.
5961
result = SamConnectWithCreds(ref unicodeServerName, out serverHandle, accessMask, objectAttributes: IntPtr.Zero, ref authIdentity, servicePrincipalName: null, out bool _);

0 commit comments

Comments
 (0)