The Surly Admin

Father, husband, IT Pro, cancer survivor

Disabling Users by CSV

Really interesting script came by recently on Spiceworks, written in vbScript.  As you know I like to take these scripts and see what I can do with them in Powershell with a special eye towards shortening it!  This script, Disable User Account from Text file, was written by stubar and is a really well done script.  I don’t want to take anything away from that.  But with Powershell we can do the same thing, and often do more with less code.  Here’s what I came up with.

Quick and Dirty

First off, we can accomplish this entire thing in a very short one line script.  Not a bad conversion rate, 99 lines of code down to one:

Get-Content c:\utils\users.txt | % { Set-ADUser $_ -Enabled:$false }

To be fair, there are a couple of things I’ve left out.  First you need to install RSAT and the Powershell prompt must have the ActiveDirectory module already loaded.  I do this automatically in my $profile so for me this command would work right out of the box.  You might have to do a little configuring first.  Also, there’s really no feedback on this one line, so while it would work you wouldn’t have much to show for it.

Nice and Pretty

Let’s improve this code a bit to put some error checking in it and some logging.  The first thing we’re going to need is some variables to define where the input file is and where we want to store the log files:

Param(
 [string]$InputFile = "c:\utils\users.txt",
 [string]$OutputFile = "c:\utils\disabledusers.txt"
 )

I’ve set some defaults so you can alter those to fit your environment.  Since they’re also parameters I can change the script on the fly by just specifying the parameters when I call the script.  Next we’ll make sure the ActiveDirectory module is loaded and set some variables we’ll need for logging.

Import-Module ActiveDirectory -ErrorAction SilentlyContinue
$Result = @()
$DisabledCount = 0
$AlreadyDisabledCount = 0
$NotFoundCount = 0

Then we’ll need to setup our loop, load the user object using Get-ADUser, disable the user, set the log and increment the count.

Get-Content $InputFile | ForEach-Object {
 $User = $null
 $User = Get-ADUser $_
 If ($User)
 { If ($User.Enabled)
 { $User | Set-ADUser -Enabled $false
 $Result += New-Object PSObject -Property @{
 User = $User.Name
 DN = $User.distinguishedName
 Status = "Disabled"
 }
 $DisabledCount ++
 }

I did run into a little problem at this point.  Typically when you call a cmdlet and it fails it will return a $null value, but Get-ADUser doesn’t behave that way.  If the user isn’t found it won’t do anything so what would happen is the $User variable would remain the value it was (so the last user).  This is bad and would make for some pretty confusing log files so I just had to set $User to $null before issuing the Get-ADUser cmdlet.

Now we need to put some ELSE clauses in there if the user is already disabled, or if we can’t find them at all:

Else
 { $Result += New-Object PSObject -Property @{
 User = $User.Name
 DN = $User.distinguishedName
 Status = "Already disabled"
 }
 $AlreadyDisabledCount ++
 }
 }
 Else
 { $Result += New-Object PSObject -Property @{
 User = $_
 DN = "N/A"
 Status = "User not found"
 }
 $NotFoundCount ++
 }

OK, we’re just about done.  We’ve looped through the contents of Users.txt and disabled every user that was in there, we’ve logged that, whether they were already disabled and whether we could find them or not.  So now let’s output some of this great information.

Interesting problem with PSObjects in Powershell 2.0.  When you define them you have no control what order they come out.  So even though the code says User then DN then Status, those three properties could come out in any order on our output.  Powershell 3.0 has fixed this with an [ordered] option when declaring the property but for now we’ll stick with Powershell 2.0 since that’s what most people are still using.  That said, how do we fix this random ordered thing?  Use the Select cmdlet, of course!  Then we’ll just output our log to my favorite Out-Gridview and to a CSV just for fun.  Last I just write to the host some of the counts we got.  Not really needed information but interesting.

$Result = $Result | Select User,Status,DN
$Result | Out-GridView
$Result | Export-CSV $OutputFile -NoTypeInformation
cls
Write-Host "Accounts in file: $((Get-Content $InputFile).Count)"
Write-Host "Users Disabled: $DisabledCount"
Write-Host "Users Already Disabled: $AlreadyDisabledCount"
Write-Host "Users Not Found: $NotFoundCount"

And we managed all of that in 50 lines, almost exactly half of the original vbScript and got a lot more logging information out of it.  Not a bad day’s work.  Still, the one liner was pretty cool too!  🙂

Full Source

Here’s the full source if you want it:

Param(
 [string]$InputFile = "c:\utils\users.txt",
 [string]$OutputFile = "c:\utils\disabledusers.txt"
 )
Import-Module ActiveDirectory -ErrorAction SilentlyContinue

$Result = @()
$DisabledCount = 0
$AlreadyDisabledCount = 0
$NotFoundCount = 0

Get-Content $InputFile | ForEach-Object {
 $User = $null
 $User = Get-ADUser $_
 If ($User)
 { If ($User.Enabled)
 { $User | Set-ADUser -Enabled $false
 $Result += New-Object PSObject -Property @{
 User = $User.Name
 DN = $User.distinguishedName
 Status = "Disabled"
 }
 $DisabledCount ++
 }
 Else
 { $Result += New-Object PSObject -Property @{
 User = $User.Name
 DN = $User.distinguishedName
 Status = "Already disabled"
 }
 $AlreadyDisabledCount ++
 }
 }
 Else
 { $Result += New-Object PSObject -Property @{
 User = $_
 DN = "N/A"
 Status = "User not found"
 }
 $NotFoundCount ++
 }
}
$Result = $Result | Select User,Status,DN
$Result | Out-GridView
$Result | Export-CSV $OutputFile -NoTypeInformation
cls
Write-Host "Accounts in file: $((Get-Content $InputFile).Count)"
Write-Host "Users Disabled: $DisabledCount"
Write-Host "Users Already Disabled: $AlreadyDisabledCount"
Write-Host "Users Not Found: $NotFoundCount"
Advertisement

October 1, 2012 - Posted by | PowerShell | , ,

20 Comments »

  1. This is awesome! Exactly what I have been searching for. I am new to the world of scripting and really appreciate your post. Simply Genius!
    Question? What does the users.txt file look like? First Name Last Name ? samACCOUNTname? I am going to try and make this work for our network. Any info on the input text file would be greatly appreciated.

    Comment by Joe | December 5, 2012 | Reply

  2. Hi Joe, glad it’s useful for you! The users.txt file simply has a list of SamAccountName’s in it, one user per line.

    Comment by Martin9700 | December 5, 2012 | Reply

  3. Instead of a text file how can I get this script to use a CSV file as the input file. My CSV file has two columns Last Name and First Name. Any suggestions would be greatly appreciated.

    Comment by Joe | December 5, 2012 | Reply

    • Not too difficult. Change line #12 from Get-Content to Import-csv. Then change line 14 to:

      $User = Get-ADUser -Filter {Givenname -eq $_.Firstname -and Surname -eq $_.Lastname}

      For testing purposes, I’d recommend changing line #17 and adding a -WhatIf at the end until you’re sure it’s doing what you want.

      Comment by Martin9700 | December 5, 2012 | Reply

    • Make sure the header row on your CSV read Firstname and Lastname (no space).

      Comment by Martin9700 | December 5, 2012 | Reply

      • Thanks for the info above this is very helpful. I’m made the changes as suggested cannot seem to have the script find my test user within my domain.
        Results are :
        Accounts in File: 2
        Users Disabled: 0
        Users Already Disabled: 0
        Users Not Found: 1
        https://thesurlyadmin.com/2012/10/01/disabling-users-by-csv/#respond
        The Test user does exist on my domain controller. Do I need to declared my domain name in the script?

        Comment by Joe | December 5, 2012

      • The change to line 14 is giving an error: See below:

        The term ‘Get-ADUser’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is c
        orrect and try again.
        At C:\Scripts\DisableAD.ps1:14 char:20
        + $User = Get-ADUser <<<< -Filter {GivenName -eq $_.Firstname -and Surname -eq $_.Lastname}
        + CategoryInfo : ObjectNotFound: (Get-ADUser:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CommandNotFoundException

        Comment by Joe | December 6, 2012

  4. Here is my text file output
    “User”,”Status”,”DN”
    “@{Firstname=Bob; Lastname=Tester}”,”User not found”,”N/A”

    Comment by Joe | December 5, 2012 | Reply

  5. Is there a way to add an option to moved the disabled account to a “Terminated” OU?

    Comment by John Malon | April 18, 2014 | Reply

    • Should be easy enough. Take a look at Move-ADObject.

      Comment by Martin9700 | April 18, 2014 | Reply

  6. Using your main script and advice you’ve given others I’ve tried to add steps to move the terminated users into a Terminated OU, but receive the following errors:

    At C:\Users\username\Desktop\DisableEmployeesAccounts.ps1:31 char:7
    + $User | Move-ADObject -TargetPath “OU=Terminated,DC=domain,DC=local” …
    + ~
    Missing ‘=’ operator after key in hash literal.
    At C:\Users\username\Desktop\DisableEmployeesAccounts.ps1:33 char:26
    + $AlreadyDisabledCount ++
    + ~
    Missing ‘=’ operator after key in hash literal.
    At C:\Users\username\Desktop\DisableEmployeesAccounts.ps1:16 char:2
    + { If ($User.Enabled)
    + ~
    Missing closing ‘}’ in statement block.
    At C:\Users\username\Desktop\DisableEmployeesAccounts.ps1:12 char:40
    + Import-csv $InputFile | ForEach-Object {
    + ~
    Missing closing ‘}’ in statement block.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEqualsInHashLiteral

    Code:

    Param(
    [string]$InputFile = “c:\utils\TermedUsers.csv”,
    [string]$OutputFile = “c:\utils\disabledusers.txt”
    )
    Import-Module ActiveDirectory -ErrorAction SilentlyContinue

    $Result = @()
    $DisabledCount = 0
    $AlreadyDisabledCount = 0
    $NotFoundCount = 0

    Import-csv $InputFile | ForEach-Object {
    $User = $null
    $User = Get-ADUser -Filter {Givenname -eq $_.Firstname -and Surname -eq $_.Lastname}
    If ($User)
    { If ($User.Enabled)
    { $User | Set-ADUser -Enabled $false -WhatIf
    $User | Move-ADObject -TargetPath “OU=Terminated,DC=domain,DC=local” -WhatIf
    $Result += New-Object PSObject -Property @{
    User = $User.Name
    DN = $User.distinguishedName
    Status = “Disabled”
    }
    $DisabledCount ++
    }
    Else
    { $Result += New-Object PSObject -Property @{
    User = $User.Name
    DN = $User.distinguishedName
    Status = “Already disabled”
    $User | Move-ADObject -TargetPath “OU=Terminated,DC=domain,DC=local” -WhatIf
    }
    $AlreadyDisabledCount ++
    }
    }
    Else
    { $Result += New-Object PSObject -Property @{
    User = $_
    DN = “N/A”
    Status = “User not found”
    }
    $NotFoundCount ++
    }
    }
    $Result = $Result | Select User,Status,DN
    $Result | Out-GridView
    $Result | Export-CSV $OutputFile -NoTypeInformation
    cls
    Write-Host “Accounts in file: $((Get-Content $InputFile).Count)”
    Write-Host “Users Disabled: $DisabledCount”
    Write-Host “Users Already Disabled: $AlreadyDisabledCount”
    Write-Host “Users Not Found: $NotFoundCount”

    Comment by John Malon | April 30, 2014 | Reply

  7. Hi John, line #31 shouldn’t even be there. You have a command to move the user within the creation of a Object which is a syntax error. Also, that seems to be the same line as line #18 so I’m not sure why you put it in there? Also, don’t forget that the -WhatIf statements on the disable and Move-ADObject will prevent anything from actually happening so you’ll need to remove them to make stuff actually move.

    Comment by Martin9700 | April 30, 2014 | Reply

  8. Martin,

    Thank you for your help! I’ll give that a try.

    Comment by John Malon | April 30, 2014 | Reply

  9. Still using -WhatIf for now, but below is what I’ve changed it to. It runs without errors.

    Param(
    [string]$InputFile = “c:\utils\TermedUsers.csv”,
    [string]$OutputFile = “c:\utils\disabledusers.txt”
    )
    Import-Module ActiveDirectory -ErrorAction SilentlyContinue

    $Result = @()
    $DisabledCount = 0
    $AlreadyDisabledCount = 0
    $NotFoundCount = 0

    Import-csv $InputFile

    ForEach ($usernames in $users) {
    $User = $null
    $User = Get-ADUser -Filter {Givenname -eq $usernames.Firstname -and Surname -eq $usernames.Lastname}

    If ($User)
    { If ($User.Enabled)
    { $User | Set-ADUser -Enabled $false -WhatIf
    $User | Move-ADObject -TargetPath “OU=Terminated,DC=domain,DC=local” -WhatIf
    $Result += New-Object PSObject -Property @{
    User = $User.Name
    DN = $User.distinguishedName
    Status = “Disabled”
    }
    $DisabledCount ++
    }
    Else
    { $Result += New-Object PSObject -Property @{
    User = $User.Name
    DN = $User.distinguishedName
    Status = “Already disabled”
    }
    $AlreadyDisabledCount ++
    }
    }
    Else
    { $Result += New-Object PSObject -Property @{
    User = $_
    DN = “N/A”
    Status = “User not found”
    }
    $NotFoundCount ++
    }
    }
    $Result = $Result | Select User,Status,DN
    $Result | Out-GridView
    $Result | Export-CSV $OutputFile -NoTypeInformation
    cls
    Write-Host “Accounts in file: $((Get-Content $InputFile).Count)”
    Write-Host “Users Disabled: $DisabledCount”
    Write-Host “Users Already Disabled: $AlreadyDisabledCount”
    Write-Host “Users Not Found: $NotFoundCount”

    Comment by John Malon | April 30, 2014 | Reply

  10. I think I finally got it working well:

    Param(
    [string]$InputFile = “c:\utils\TermedUsers.csv”,
    [string]$OutputFile = “c:\utils\disabledusers.txt”
    )
    Import-Module ActiveDirectory -ErrorAction SilentlyContinue

    $Result = @()
    $DisabledCount = 0
    $AlreadyDisabledCount = 0
    $NotFoundCount = 0

    $Users = Import-csv $InputFile

    ForEach ($usernames in $users) {
    $User = $null
    $fname = $usernames.Firstname
    $lname = $usernames.Lastname
    $User = Get-ADUser -Filter {(Givenname -eq $fname ) -and (Surname -eq $lname )}

    If ($User)
    { If ($User.Enabled)
    { $User | Set-ADUser -Enabled $false -WhatIf
    $User | Move-ADObject -TargetPath “OU=Terminated,DC=domain,DC=local” -WhatIf
    $Result += New-Object PSObject -Property @{
    User = $User.Name
    DN = $User.distinguishedName
    Status = “Disabled”
    }
    $DisabledCount ++
    }
    Else
    { $Result += New-Object PSObject -Property @{
    User = $User.Name
    DN = $User.distinguishedName
    Status = “Already disabled”
    }
    $AlreadyDisabledCount ++
    }
    }
    Else
    { $Result += New-Object PSObject -Property @{
    User = $_
    DN = “N/A”
    Status = “User not found”
    }
    $NotFoundCount ++
    }
    }
    $Result = $Result | Select User,Status,DN
    $Result | Out-GridView
    $Result | Export-CSV $OutputFile -NoTypeInformation
    cls
    Write-Host “Accounts in file: $((Get-Content $InputFile).Count)”
    Write-Host “Users Disabled: $DisabledCount”
    Write-Host “Users Already Disabled: $AlreadyDisabledCount”
    Write-Host “Users Not Found: $NotFoundCount”

    Comment by John Malon | April 30, 2014 | Reply

  11. It works great unless you have users, and of course we have some, that have the same first and last names, but different middle names. If for example John A. Smith is being terminated, both John A. Smith and John B. Smith would also be disabled with the present script. Ahhh, the fun never ends. 🙂 But is should be somewhat easy to add a column to the csv and something in the script to check the middle initial to determine if an account needs to be disabled.

    Comment by John Malon | April 30, 2014 | Reply

    • I had to actually use the script this weekend and removed the -WhatIf statements. It worked great. I manually termed the user that had a duplicate first and last name of a not-to-be termed user. Thank you for your hard work on this!!!!

      Comment by John Malon | May 12, 2014 | Reply

  12. Hi,

    I have three OUs to search if account exist based on firstname and lastname of users. I’m having trouble looping through those OU, can you advise how i can do that?

    Thx

    Comment by vluu | June 1, 2016 | Reply

  13. Hi,

    following up on my question, i think i figured it out. prelimimary test looks good, unless you have something more efficient

    $User = $SearchBaseUS,$SearchBaseCA,$SearchBaseBR | ForEach {Get-ADUser -SearchBase $_ -Filter {(Givenname -eq $fname ) -and (Surname -eq $lname )}}

    Comment by vluu | June 1, 2016 | Reply

  14. Works a treat many thanks ! 🙂

    Comment by James Crichton | December 16, 2016 | Reply


Leave a Reply to Martin9700 Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: