18

On Windows Server 2008 R2, I have a standard (non-administrator) local user (not an Active Directory account, though the server is in a domain) who has access to the server only via PowerShell Remoting. The user cannot login via RDP.

I would like this user to be able to change their password. The 'net user' command requires administrator rights, even if the user is trying to change their own password.

How can a standard user change their password from the command line?

elijahbuck
  • 450
  • 1
  • 3
  • 8

4 Answers4

20

Here's some PowerShell code to do what you're looking for with domain accounts:

param (
    [string]$oldPassword = $( Read-Host "Old password"),
    [string]$newPassword = $( Read-Host "New password")
)

$ADSystemInfo = New-Object -ComObject ADSystemInfo
$type = $ADSystemInfo.GetType()
$user = [ADSI] "LDAP://$($type.InvokeMember('UserName', 'GetProperty', $null, $ADSystemInfo, $null))"
$user.ChangePassword( $oldPassword, $newPassword)

The ASDI provider also supports the syntax WinNT://computername/username for the ChangePassword() method. The ADSystemInfo object, however, won't work for machine-local accounts, so just retrofitting the code above with WinNT://... syntax isn't workable.

(Anybody want to suggest an edit w/ code to differentiate between local and domain accounts?)

On a completely different tack, the old NetUserChangePassword API will work with local (and domain, provided you specify the domain name in NetBIOS syntax) accounts, too:

param (
    [string]$oldPassword = $( Read-Host "Old password"),
    [string]$newPassword = $( Read-Host "New password")
)

$MethodDefinition = @'
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern bool NetUserChangePassword(string domainname, string username, string oldPassword, string newPassword);
'@

$NetAPI32 = Add-Type -MemberDefinition $MethodDefinition -Name 'NetAPI32' -Namespace 'Win32' -PassThru

$NetAPI32::NetUserChangePassword('.', $env:username, $oldPassword, $newPassword)

This code assumes you're changing a password on the local machine (".").

Evan Anderson
  • 141,881
  • 20
  • 196
  • 331
  • 1
    You beat me to it, but I win for conservation of characters ;) Any reason you know of that the extra parts you used are necessary? Or just to be more formal and proper? – charleswj81 Jan 28 '14 at 19:05
  • 1
    You definitely get the code golf award. I'm just ol' formal and proper... Actually, it's mainly make the script a little more usable to others who might be cut-and-paste types. – Evan Anderson Jan 28 '14 at 19:09
  • 1
    @charleswj81 - Evan's answer is way more complete. It's a standalone `PS1` script that can be called with or without parameters. It's much more readable too. Code is all about humans understanding what someone else has written, not the computer. Neither solution will be faster than the other. – Mark Henderson Jan 28 '14 at 19:10
  • 1
    This looks promising, but I should clarify that the account is local to the system. I attempted the following: `([ADSI]'WinNT://localhost/USERNAME').ChangePassword("OLDPASS", "NEWPASS")` But that returned 'The password does not meet the password policy requirements...'. The new password does meet those requirements though. – elijahbuck Jan 28 '14 at 19:39
  • 1
    Actually, after adding the user to 'Remote Desktop Users' and attempting the password change that way, I get the same error. I believe that the correct way to change a local account is: `([ADSI]'WinNT://localhost/USERNAME').ChangePassword("OLDPASS", "NEWPASS")` – elijahbuck Jan 28 '14 at 19:46
  • I doubt that you want "localhost", though. Typically the local computer is specified in Microsoft APIs as ".". – Evan Anderson Jan 28 '14 at 19:54
  • 'localhost' does work. '.' also works and is perhaps preferable. As an aside, my issue with the password policy requirement was that the minimum password age was set to 1 day. – elijahbuck Jan 28 '14 at 20:01
  • 1
    Dont use "Bool" - with bool you cant distinguish between failure and success.... with long you get return codes you can use: `public static extern ***long*** NetUserChangePassword(string domainname, string username, string oldPassword, string newPassword);` –  Mar 01 '16 at 10:21
9

This is actually pretty simple in PowerShell:

([ADSI]'LDAP://CN=User,CN=Users,DC=domain').ChangePassword('currentpassword','newpassword')
charleswj81
  • 2,453
  • 15
  • 18
3

I tried both of the answers above to no avail, for changing the password of a local admin that is not domain-joined. Digging through the comments yielded what I needed though.

For the second part of the currently accepted answer, you want to update the signature to use long instead of bool return value, and these can be troubleshooted over at the system error codes docs. So you end up with:

param (
    [string]$oldPassword = $( Read-Host "Old password"),
    [string]$newPassword = $( Read-Host "New password")
)

$MethodDefinition = @'
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern **long** NetUserChangePassword(string domainname, string username, string oldPassword, string newPassword);
'@

$NetAPI32 = Add-Type -MemberDefinition $MethodDefinition -Name 'NetAPI32' -Namespace 'Win32' -PassThru

$NetAPI32::NetUserChangePassword('.', $env:username, $oldPassword, $newPassword)

However, that did not work for me. The error codes alternated between 86 and 2221, depending on how I set up the parameters. Was about to give up and dug more into the comments, and finally found success in doing:

([ADSI]'WinNT://./USERNAME').ChangePassword("OLDPASS‌​", "NEWPASS")

Absolutely ridiculous that simple CHANGING of a local admin password is so complicated in Powershell. If you store securestrings at all on your system, then updating the password must be done with supplying the old password, or risk losing ability to properly decrypt those secure strings!

gnalck
  • 131
  • 3
0

This combines info from multiple contributors in this topic and the cited topic after the code

# For local system userids, try entering the hostname for the User Domain
param (
    [string]$domain =      $( Read-Host "User Domain "),
    [string]$userName =    $( Read-Host "User Name   "),
    [string]$oldPassword = $( Read-Host "Old password")
)

$punc = @(0x2e,0x2b,0x2a,0x2d,0x5f)
$digits = 48..57
$upper = 65..90
$lower = 97..122
$combined = $punc + $upper + $digits + $lower

function generatePass {

    $pass = ""
    Get-Random -Count 1 -InputObject $lower | %{$pass += [char]$_}
    Get-Random -Count 1 -InputObject $upper | %{$pass += [char]$_}
    Get-Random -Count 1 -InputObject $punc | %{$pass += [char]$_}
    Get-Random -Count 1 -InputObject $digits | %{$pass += [char]$_}
    Get-Random -Count 19 -InputObject $combined | %{$pass += [char]$_}

    
    return $pass
}

$newPassword = generatePass

write $newPassword

$MethodDefinition = @'
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern long NetUserChangePassword(string domainname, string username, string oldPassword, string newPassword);
'@

$NetAPI32 = Add-Type -MemberDefinition $MethodDefinition -Name 'NetAPI32' -Namespace 'Win32' -PassThru

$winErrCode = $NetAPI32::NetUserChangePassword($domain, $userName, $oldPassword, $newPassword)
# You want to see 0/Success as the final output line
write $winErrCode

Ideas for the password generation were gathered from this topic and then simplified. https://stackoverflow.com/questions/37256154/powershell-password-generator-how-to-always-include-number-in-string

englebart
  • 101
  • 1