Monday, July 16, 2012

A good time

Time is what keeps everything from happening all at once.  I think Confucius said that.  Or maybe it was one of those stoner guys I met at college.  Either way most humans agree that time is frequently useful.


Sometimes you need to know how long it takes a script to run.  This comes in handy if you are comparing various techniques to accomplish a task and want to find the most efficient approach.  Or if you need to benchmark a script with a small data set to estimate how long it will take with the full data set.  Or maybe you are just curious like a cat.

vbScript has the function Time that returns the (you guessed it) current system time.  It is returned as a datetime value so use the DateDiff function to compare the difference between two values.  My sample script is:

StartTime = Time
wscript.echo StartTime


for i = 1 to 100000000
next


EndTime = Time
wscript.echo EndTime


TotTime = DateDiff("s",StartTime,EndTime)wscript.echo TotTime

wscript.echo "The operation took " + cStr(TotTime) + " seconds."


The Time and DateDiff functions only offer time accurate to the second.  Instead we should use the Timer function.  This not only offers greater accuracy but it also lets us do simple math to see the results.

StartTime = Timer
wscript.echo StartTime


for i = 1 to 50000000
next


EndTime = Timer
wscript.echo EndTime


TotTime = cStr(EndTime-StartTime)
wscript.echo "The operation took " + TotTime + " seconds."



The DOS environment variable %TIME% is accurate to the hundredth of a second.  You can use this if you want to time the processing of a batch file, but you have to do some serious parsing of the variable by using the FOR command.  In this example I use FOR /F to break the %TIME% string into 4 pieces (hours, minutes, seconds, hundredths) and do a bunch of math to turn the whole thing into the number of hundredths of seconds since midnight.  (The two FOR /F commands are wrapped in the example below.)

echo OFF
echo %TIME%
for /F "tokens=1-4 delims=:." %%A in ('echo %TIME%') do set /A Start=(%%A*60*60*100)+(%%B*60*100)+(%%C*100)+%%D


for /L %%X in (0,1,10000) do rem

for /F "tokens=1-4 delims=:." %%A in ('echo %TIME%') do set /A Stop=(%%A*60*60*100)+(%%B*60*100)+(%%C*100)+%%D
ECHO ON


set /A TotTime=%Stop%-%Start%
set /A Secs=%TotTime%/100
set /A Hund=%TotTime% %% 100
echo "The operation took %Secs%.%Hund% seconds"


set Hund=0%Hund%
set Hund=%Hund:~-2%

echo "The operation took %Secs%.%Hund% seconds"

The math is pretty straightforward and I can calculate the elapsed time with simple subtraction.  I use the modulo operator (%) to separate hundredths from seconds.  But note that the environment variables are strings, so I still need to add a leading zero then take the last 2 characters of the string to make sure I have a 2 digit integer after the decimal point.


But PowerShell is the undisputed champ of scripting and and timing script execution is no exception.  Sure, there are cmdlets for finding the current time and methods for doing datetime math.  But why go to all that trouble when you have the Measure-Command cmdlet?  Enclose the code you need to benchmark in brackets and PowerShell gives you detailed timing information.



As always, the cmdlet returns an object that you can process through the pipeline.  Here I use the shortcut of finding just the number of seconds it took to run the code.


If you have plenty of time then you have time to kill. But if you are out of time you are out of luck. So don't take your time for granted unless you are granted more time, in which case you can take all the time you need.  So until next time, take your time and take care.




Monday, July 2, 2012

The shortest distance

Everybody likes shortcuts.  If you can save yourself some time and effort, why not take the opportunity?

Powershell offers several ways to save yourself some typing and shorten your command lines and scripts.  Plus you almost never blow yourself up.

One way is to string together all of the slicing and dicing you might need to do.  If a method for an object returns an array and I know I need a specific element in that array I can specify that element  right after the method invocation.  And I can call a method right from that array element.  Allow me to demonstrate.

In a previous example I had to parse the DistinguishedName for a user.  I model my test domain based on a former employer who managed users and computers by location.  Delegations were done so support staff at each site could manage their objects.  My OU structure looks like this:

In order to find the site name for an arbitrary user account I can assign a variable at each step to parse the DistinguishedName.  In this example I also show the value of the variables to confirm each step:

But I don't need to use all those variables.  This can be processed in a single line.  Below I build up each step.  The last line shows how I combine all of these steps into a single command:

Another handy shortcut is to enclose a command in parentheses and do the same slicing and dicing.  In the above example I don't even need to assign the Get-ADUser output to a variable.  I can use the command
 (Get-ADUser AlAlpha).DistinguishedName.split(',')[2].substring(3)

Letting PowerShell process things in parentheses has many uses and works for all cmdlets.  You can nest some really crazy crap and it will still work.  In the following example I know I have a user in Atlanta with the last name Alpha and I'm not sure what the account name is but I have to change his first name to Allen.  The first command get-aduser -searchbase "OU=Atlanta,OU=Locations,DC=toddp,DC=local" -filter * | where {$_.surname -eq 'Alpha'} shows how I can do this search.  In the second command I enclose that first command in parentheses and use that as the identity for my Set-ADUser command.  I call Get-ADUser one last time to show the change was made in the GivenName attribute.

Another PowerShell shortcut is to use aliases for the cmdlets. 
Here we see all of the aliases that start with the letter "g".  Two aliases I use frequently are gm for Get-Member and gwmi for Get-WmiObject.


In this example the Get-ADUser output is piped through the Get-Member cmdlet using the alias GM.

You can create your own aliases using the Set-Alias cmdlet.
After using Import-Module (or its alias, ipmo) to import the ActiveDirectory cmdlets I use Set-Alias to create the alias gadu for the Get-ADUser command.  Note that an alias can only be for a cmdlet, not a cmdlet and its parameters.  You can work around this limitation by using a function as shown in the last example for Set-Alias.


There are two other aliases that you may encounter.

There is a default alias for ForEach-Object which is the percent sign (%) and an alias for Where-Object which is the question mark (?).  This will come in handy.

How cool is this?  I pipe Get-Process through the ForEach-Object shortcut to pull out the ProcessName and use the Where-Object shortcut to show just the processes starting with the letter "c".  That's good enough for me.

In Powershell 3 there is an implied ForEach-Object for arrays.  Instead of piping the array through ForEach-Object cmdlet or shortcut you just enclose the object in parentheses and process the properties you need.
These are the processes running on my Windows 8 demo VM.  But if I need to grab just the process name I can use the syntax (Get-Process).ProcessName
Pretty sweet.  There are some other handy shortcuts coming in PowerShell 3.

So we have all these nice shortcuts.  Shouldn't we use them all the time?  The rule of thumb from our friend Don Jones is that if you are writing a quick and dirty command for yourself, then the shortcuts can save you time and effort.  But scripts with shortcuts look more cryptic and are harder to read.  So if you are creating a more invovled script that will be used by someone else you should be considerate and make it more accessible by avoiding shortcuts. 

Knowing these shortcuts and aliases (or at least how to find what is being aliased) helps you be more efficient.  And if you come across a script written by someone else who used them you won't be caught off guard