Thursday, February 19, 2009

Here's a simple one, you lazy bastard. Get the uptime of all the servers in your domain.

**Disclaimer** Since you are the worst admin ever, you already know that we're talking about Windows boxes here. Windows boxes that will respond to WMI calls, to narrow it down a little further **Disclaimer Over**

So your boss is yelling at you about OS patches, or more specifically, the lack of application of said patches. What's new? Your boss is always yelling at you; this time it happens to be about patches. Last time the boss was yelling at you it was probably something you screwed up, this time the yelling is about something you haven't screwed up yet.

So, as the worst admin ever, what do you do? You find a couple old servers to patch to get the heat off your back so you can get back to watching the Woot-off.

So how do you find a few old servers? Your memory of host names and hardware is sub-goldfish level, not to mention your dyslexia, so don't even try to ballpark this one. Fire up Hyena, pump all your servers out to a .csv file, then fire up this little gem to find out who hasn't been rebooted for a while. Start up the incredibly fast software update process, then get back to the Woot-off.

##Blank out previous log file
out-file serveruptime.txt

import-csv Servers.csv | foreach {
$server = $_.server

##Check for LastBootupTime
$Uptime = (get-wmiobject win32_operatingsystem -computername $server).lastbootuptime

if ($Uptime -gt 0)
{
$Year = ($Uptime).substring(0,4)
$Month = ($Uptime).substring(4,2)
$Day = ($Uptime).substring(6,2)
"$Server,$Month/$Day/$Year" | out-file serveruptime.txt -append
}
else
{
"$Server,WMI Failure" | out-file serveruptime.txt -append
}
}

Tuesday, December 23, 2008

99 Problems and Well, You Know the Rest

So the previous script was the first full-fledged script that I wrote in Powershell. It shows, huh? There's probably a better way to do it, but hey, shut up. I have almost zero shell script, programming, batch file, human anatomy, and civil war knowledge, so let's just say I'm starting on the ground floor. Or the basement. Of a building 4 blocks away from the one I want to be in.

Here's the problems that I ran into while trying to write this. Maybe his will keep you from jumping off a bridge because you can't figure out how to script your way out of a directory called "Paper Sack". Maybe not, but if you're going to be a bad admin, you should remember that no matter what, someone out there is worse than you. Scary, right?!?

The first problem I ran into was turning the results of a query into something that I could use for if/else logic. I'd run something like:

get-qaduser worstadminever | select samaccountname

and get something back like:

SamAccountName
--------------
WorstAdminEver

If I tried to compare that to a known value it would never match, because of the column heading. I needed a way to return just the value, not the header row. Now, all of you know how to do this already, so maybe I shouldn't bother, but I don't care. When you declare the variable like:

$DumbUser = get-qaduser worstadminever | select samaccountname

All you have to do is follow it up with a re-declaration like so:

$DumbUser = $DumbUser.SamAccountName

This clears out all the column heading crap and gives you everything you want (besides an increase in pay). After that, it's easy to match the results of a query to known values, count the length of the string, etc.

Being able to perform this logic allowed me to check the query to see if a user account was returned. Not everyone in our HR system has an AD account, so I have to assume that I'm going to be making some queries that return with zero information. After checking to see if a user name was returned, I proceeded to look up the values the account had for extensionattribute1 and 2. If the fields don't need to be updated, then don't update them, you spaz. Sometimes it's handy to see when an account has been last modified, and if your moronic script hits everyone every day, you lose.

The last thing I did was to speed up the script by having get-qaduser only give me back the fields I want with the arguments:

-dontusedefaultincludedproperties -includedproperties extensionattribute1,extensionattribute2

You've got to hand it to Quest, you know what those arguments are doing. It's verbose, but it's better than anything you'll ever do.

All in all, these modifications dropped my runtime to about 2 hours.

Use Powershell to Update Active Directory from a CSV File

So someone actually gave you Domain Admin credentials? Really? Wow. I sure wouldn't have done that. I might take my own permissions away just to play it safe. Anyway, you've somehow managed to get your HR department to give you a .csv file with everyone's employee ID and department number, and you need to get all of this data in AD. This will work with an employee ID field or any other unique identifier.

Here's the variables of my situation that make all of this come together:
1. Employee ID number, this is our unique identifier. It may be a good idea to pull a query from your domain (using Hyena or csvde) to make sure that your unique identifier field does not contain duplicates. If you are as crappy as an admin as I think you are, then there's probably a ton of them. Hell, you probably can't even spell "duplicate" without the squiggly red line in MS Word telling you that it's wrong. Get off your butt and clean these up.
2. The fields to target for the update. In my case, this is extensionattribute1 and 2, which holds a department number and a cost center for long distance calling. But that's my deal, you can have it update any field that HR can give you in the .csv file. Fax numbers, Addresses, City, State, Blood Alcohol Level, go nuts.
3. I have a text file (unmatched.txt) that I append with a list of users that do not line up between HR and AD. In our environment it's possible to have someone in HR, but not AD since everyone doesn't get an account automatically. If I am feeling super-extra-motivated, which I am not, I check unmatched.txt just to make sure everything looks OK. It doesn't hurt.
4. This is the most important one: install the Quest add-ins for Powershell. If you're running Exchange 2003 then you've probably already installed these to get some of the functionality that the 2007 upgrade will get you.

To run the script, I save the .csv file to the same directory as my script (I'm to lazy to save it anywhere else). In this case the .csv file is called "updates.csv". Wow.

Earlier versions of this script took a long time to run, over 8 hours for about 4000 users. I trimmed it down so that it only gets the fields necessary, and performs some simple logic to see if it needs to update the field. Impressed? No? Too bad. The cleaned-up version of the script takes about 2 hours to update 4000 users.

Bored yet? OK, fine. Here's the script:

# Get date for log file
$day = Get-Date -UFormat "%Y%m%d"

import-csv updates.csv | foreach {

##Get values from CSV
$ID = $_.ID
$DeptID = $_.DeptID
$Cost = $_.Cost
$Name = $_.Name

##Lookup user and get ID, DeptID and Cost
$User = get-qaduser -dontusedefaultincludedproperties -includedproperties extensionattribute1,extensionattribute2 -oa @{employeeID="$ID"} | select-object SamAccountName,extensionattribute1,extensionattribute2
$Username = $User.SamAccountName

if ($Username.length -gt 1)
{
$UserDeptID = $User.extensionattribute1
$UserCost = $User.extensionattribute2

##Check to see if Dept and Cost are already correct and if not, set them
if ($DeptID -eq $UserDeptID)
{"$Username ID match"}
else
{set-qaduser $Username -oa @{extensionattribute1="$DeptID"}}
if ($Cost -eq $UserCost)
{"$Username Cost match"}
else
{set-qaduser $Username -oa @{extensionattribute2="$Cost"}}
}
else
{
"$day,$Name,$ID" | out-file unmatched.txt -append
return
}
}


In the next post, I'll talk about the problems I faced when writing this script and how I managed to get my little, lazy brain to solve them.