Skip navigation

Tag Archives: scripting

As a Systems Engineer/SCCM Administrator I spend a lot of time parsing through data, and assisting support technicians in tracking down failing assets.  Now mind you, I have plenty of reports that give me the information I need to identify the machine and users and techs responsible etc, but what happens when I get a random list of employee names from a project manager that has 0 access to user ids or asset numbers for machines?  Well, I have to find that information, then spend time later pointing them to resources I’ve made available for them; but that’s another topic….

Anyway, I face both problems, I’ll receive a list of userids or usernames and have to resolve them one against another.  Well thanks to powershell I’m able to do so quickly and easily through profile functions.  Now, I’ll explain the benefits of profile functions after the code below:

 


Import-Module activedirectory

#——————————————————————–
Function Get-UserName {
[CmdletBinding()]

PARAM($USERID)
Get-ADUser $USERID | select name
}
Set-Alias gun Get-UserName
#——————————————————————–
Function Get-Userid {
[CmdletBinding()]
PARAM([string]$NAME)
$NAME = $NAME + “*”
    Get-ADUser -Filter {Name -like $NAME} | select samaccountname,name
}
Set-Alias guid Get-Userid
#——————————————————————–

 


 How do I use profile functions?!?

Powershell, much like the BASH shell in Unix/Linux, has a profile “script” so to speak at startup.  There is a global one found at:

  • %WinDir%\System32\WindowsPowerShell\v1.0\Profile.ps1
  • %WinDir%\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_Profile.ps1
  • %WinDir%\System32\WindowsPowerShell\v1.0\Microsoft.PowerShellISE_Profile.ps1

The same filename syntax is used for the user profile versions:

  • %UserProfile%\My Documents\WindowsPowerShell\Profile.ps1
  • %UserProfile%\My Documents\WindowsPowerShell\Microsoft.PowerShell_Profile.ps1
  • %UserProfile%\My Documents\WindowsPowerShell\Microsoft.PowerShellISE_Profile.ps1

See a pattern?  Simple enough right?  None of these profiles exist by default though, they must be created.  The names are fairly indicative of what they control, but here’s a breakdown:

  • Profile.ps1
    • This governs startup of both the standard powershell, and the ISE.
  • Microsoft.PowerShell_Profile.ps1
    • This governs startup of the standard powershell console only.
  • Microsoft.PowerShellISE_Profile.ps1
    • This governs startup of the ISE powershell console only.

Simple enough right?  Now, for the sake of simplicity, lets build a current user version of the profile.ps1 and save the above code to it.  Make sure you’ve installed the activedirectory cmdlet module provided with windows 7. Now launch powershell and viola you should now have the cmdlets:

  • Get-UserName
  • Get-UserID

and their aliases:

  • GUN
  • GUID

Ok, now what?

Here’s the thing about profile functions.  You can treat them like cmd-lets now.  That also means that you can script against them.  Consider them a static variable for every powershell session that you have configured with this profile.

Pretty cool huh?  One of the most powerful features of the shell is it’s configurability, and profile functions and aliases are the tip of that spear.

In the case of user name capture, or id capture, I’m but a simple gc and for-each statement away from processing the list given to me.

I hope this helps broaden your practical understanding of profiles, and gets your creative juices flowing for building your own administrative tool kits.  Happy scripting.

Advertisements

I haven’t written a scripting post in a while, but I’ve wanted to.  So in keeping with the spirit of my stick to the script posts lets look at something that is common among all scripting languages (even if the syntax isn’t).

Let’s talk about strings…….

 kitty-yarn

Awwww, but no.  These kind of strings.  In the case of scripting, I think the best way to think about it is, text, what you are reading or able to read.  They aren’t used mathematically (usually), but can and will be a huge component in your scripting.  Especially when automating things around a desktop or server environment.

Oh really?  Yes, really.  General uses for strings in a script are:

  • User messages
  • Reporting or logging
  • Comparisons
  • Explicit paths
  • Application execution
  • Conditionals

Ok, so maybe that list doesn’t look that impressive, but when you consider how much of that is done within a script, it becomes obvious the importance of string values to scripting.  It’s also important to recognize that in certain scripting environments, it’s important to define a string value as such so that it can be properly used. 

(Powershell for instance, requires you to properly define a value type to use the relevant functions… but I’m getting ahead of myself.)

So wait?  There are more than strings in a script?  Yes; Strings, Integers, and Booleans are your standard value types. Integers are numbers (math!) and Booleans are True or False.  So given those value types, perhaps it is a bit more obvious how frequently you will use string values?

So lets get into some sample code and evaluate strings some shall we?


VBScript:

strTest = "Hey, I'm a string value"

wscript.echo strTest
'Shows the string value
wscript.echo strTest&", and I've been concatenated to the value."
'& operator joins values
wscript.echo lcase(strTest)
'Lower case
wscript.echo ucase(strTest)
'Upper case
wscript.echo strReverse(strTest)
'Reverses the string
wscript.echo len(strTest)
'Gives the total length of a string
wscript.echo mid(strTest,10,8)
'Returns fix number of characters in string
wscript.echo left(strTest,11)
'11 chars from the left
wscript.echo right(strTest,13)
'13 chars from the right
'----------------------------------------------------------------
wscript.echo inStr(strTest,"a")
'Returns position of string from left to right
wscript.echo inStrRev(strTest,"a")
'Returns position of string from right to left
'----------------------------------------------------------------
a = split(strTest)
'Splits strTest by it's spaces
for each item in a
    wscript.echo item
   'echoes each dimension from the split array
next

wscript.echo a(3)&" "&a(4)

'echoes the split arrays dimensions 3 and 4
'---------------------------------------------------------------

strReturn=inputbox("Here, you try!")

if strReturn = "" then
   wscript.echo "Fine, don't play along"
else
    wscript.echo "So you said: "&trim(ucase(strReturn))&vbcrlf _
    &"Sheesh, no need to shout!"
end if
'---------------------------------------------------------------


Running the above script will give you a better understanding of what I’m about to explain.  I wanted to show some common functions in vbscript (syntax is different but these will be universal functions you will use).  The above are common string manipulation tools

 

Code explained… line by line

First we are defining our string to a variable strTest.  Now “in the wild” as it were, this string could be pulled from an object property, read from a file, registry, user input, output from another application, etc.  It’s best to define a string to a variable though, no matter the method for input.  This of course is the most direct way to do it for our example

Now we begin with the simplest string usage, output.

Now we raise the stakes a bit by joining an additional string value to our current strTest.  This action is known as concatenation.  This is a very common thing with string usage and manipulation.  Building complex values/messages/logs from various predefined and/or dynamically pulled string values.

Our next two examples have to do with manipulating case between upper and lower.  This is fairly self explanatory, and in the interest of string comparisons it’s usually a good practice (and often necessary) to force a case set, especially if the comparison function is case sensitive.

String reversal, this may not seem important initially, but makes a huge difference when you are forced to chop strings up.  The ability to reverse a string can go a long way for string chopping.  Especially if you are dealing with filenames.

The length function is another that may seem arbitrary to some, but allows for great flexibility as well in chopping up strings such as file names.  If you have a fixed number of characters to remove it’s sometimes simpler then splitting the string.  (so I wasn’t completely honest about the math stuff and strings)

Mid, Left, and Right.  These 3 can be used in conjunction with length to return a fixed number of characters from the left right, or middle (specified) of a string.  Here’s a quick easy example:

test="I am 18 chars long"
wscript.echo test
count=len(test)-4
wscript.echo right(test,count)

Very simply, we take the total length of Test and subtract it by 4, then return the sum of remaining characters from right to left of the original string.  In this example we, had a fixed value to subtract, be mindful you could use the length value of multiple strings to achieve the same type of results.

Now, InStr and InStrRev, or “In String” and “In String Reverse”.  These two functions make for great conditionals.  They, along with strComp, are excellent for determining like strings and taking action.  Especially when parsing through files and directories looking for specific returns. 

One of my favorites, especially in PowerShell, split.  Split takes a string, looks for a delimiter (space by default) and breaks the string up into an array.  Why do I like it so much?  Put simply, it allows you to quickly whittle down long path names into a single filename.  It also allows you to quickly and efficiently modify lists of data into manageable formats.  And last but not least, it can easily turn files like csv’s into an array for manipulation.

Finally, user input.  This is pretty self explanatory.  Prompt for input, receive and control input, use input.


In PowerShell, string functions are called like this:

$a=”This is a string value”

$a.ToString()

$a.ToUpper()

$a.ToLower()

$a.Replace(“a “,”my “)

$a.split(“ “)

$a.contains(“string”)

$a.StartsWith(“t”)

$a.EndsWith(“e”)

$a.Length

$a.CompareTo(“this is a string value”)

$a.Length.Equals(22)

$a.CharArray()

$a.PadLeft(“30”)

$a.PadRight(“30”)

$a.Trim()

$a.TrimEnd()

$a.TrimStart()

Given the previous examples in vbscript, you should be able to easily adapt your knowledge to using these in PowerShell.  The idea and purpose is still the same, again, the syntax is just different.


String manipulation inside Bash is, admitedly, a bit more convoluted so I won’t be touching on it in this post.  However I’d highly recommend an online source like: Mendel Cooper’s guide.  Again, the methodology will still be fairly the same, but the syntax will differ.  The largest issue with Bash is the myriad of ways of performing the string manipulation.

 

Anyway, I hope this has been informative for you.  Good luck, and happy scripting!

Now, it’s time to evaluate PowerShell.  As of late, PowerShell has become my preferred method for scripting.  PowerShell is built on top of the .NET framework.  It uses cmd-lets (commandlets) to perform the majority of it’s command line actions.  It’s also fully integrated with WMI and the .NET Framework.  It works off of Objects, which we discussed previously with VBScript.  It shares a lot of similarities to the BASH Shell, but the object based pipe is one of the more powerful features.  Objects in the pipe remain objects through to output.  This is an important element to remember if you are already familiar to the BASH shell and find yourself working in a Windows environment now (such as myself).

There’s a lot of good reading over what PowerShell is, so I’ll go ahead and start with our code and break it down as we’ve done in the past.  We’ll be introducing CASE statements this time around, as well as arguments.  I hope you enjoy it.

 Function Get-PowerShell {

The purpose of this script is to start, stop, or restart services on a single machine or list of machines read from a file.


#Service State Changer Script
#Author: Daniel Belcher

#Syntax:
#    .\set-remoteservice.ps1 stop spooler laptopname

PARAM([string]$STATE,[string]$SERVICE,[string]$TARGET)
$ErrorActionPreference = "silentlycontinue"
Function Set-RemoteService{
SWITCH($STATE){
        "Start"
            {$SET.Start()
                Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Starting`n"}
       "Stop"
            {$SET.Stop()
                Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Stopping`n"}
        "Restart"
            {$SET.Stop()
        Start-Sleep -Seconds 1
             $SET.Start()
                 Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Restarting`n"}
             }

if ($STATE -ne "Start"){if  ($STATE -ne "Stop"){if ($STATE -ne "Restart"){
    Write-Output = "Invalid service state entered`n";Break}}}

                         }
if ($SERVICE -eq "")
    {$SERVICE = Read-Host "Please enter a valid service name"}
    if ($STATE -eq "")
        {$STATE = Read-Host "You need a valid state entry. Start, Stop, or Restart"}
        if ($TARGET -eq "")
            {$TARGET = $ENV:COMPUTERNAME}       
    $FILECHECK = Test-Path $TARGET
        if ($FILECHECK -eq $TRUE){
            $READ = gc $TARGET
                ForEach ($ITEM in $READ)     {
            $PING = gwmi -Query "select * from Win32_PingStatus where Address=’$ITEM’"
    if ($PING.StatusCode -ne 0)
        {Write-Output “$ITEM not found online, or invalid hostname.`n"}
    else
        {$SET = (Get-Service $SERVICE -ComputerName $ITEM)
                        Set-RemoteService
                    Start-Sleep -Seconds 1   
 
$STATUS = (Get-Service $SERVICE.ToUpper() -ComputerName $ITEM.ToUpper()).Status
         Write-Output = "Machine: $ITEM`nService: $SERVICE`nStatus: $STATUS`n"
}
                                }
                            }   
        else
            {$PING = gwmi -Query "select * from Win32_PingStatus where Address=’$TARGET’"
    if ($PING.StatusCode -ne 0)
        {Write-Output "$TARGET not found online, or invalid hostname.";
                            break}
            $SET = (Get-Service $SERVICE -ComputerName $TARGET)
                        Set-RemoteService
                Start-Sleep -Seconds 1  

$STATUS = (Get-Service $SERVICE.ToUpper() -ComputerName $TARGET.ToUpper()).Status
        Write-Output = "Machine: $TARGET`nService: $SERVICE`nStatus: $STATUS`n"
}


Copy and paste the above code and save it as a .ps1 which is the standard file extension for PowerShell scripts.  Now, lets set our execution settings from the PowerShell command line:

Set-ExecutionPolicy unrestricted

You can now execute the script by right-clicking and run with PowerShell, or by launching it from within the shell by navigating to it’s directory and typing it’s name.

Now lets look at our interest areas:

  1. Sequence
  2. Command usage
  3. Output usage
  4. Conditional logic

Sequence:

Sequence inside PowerShell is as expected.  It’s all run inline, and requires everything called to be defined before it’s call.  Looking at the above code you will see that we call Set-RemoteService two times, and we’ve defined it at the beginning of the script.  In VBScript, or in Command, calling a sub, function, or a GOTO statement will find the appropriate section and run that code.  So being aware of how your scripts process instructions is very important to their functionality.

Now for something new, arguments.  Arguments are, more or less, are user defined variables.  They are dynamic, and as such, need to be controlled to some degree.  So a fair amount of the script sequence is spent insuring that the variables passed are useable.  With PowerShell you are capable of defining the variable type.  This gives you incredible control and removes one more potential for unforeseen errors.

PARAM([string]$STATE,[string]$SERVICE,[string]$TARGET)

As you can see, with the PARAM statement we are opening 3 variables for input.  They are $STATE, $SERVICE, $TARGET.  We are also insuring whatever is entered is a string value.  So that leaves us with 2 issues.  The user needs to enter something, and it needs to be useable.  We will discuss this more in our conditional logic.

Command usage:

As we discussed at the beginning, PowerShell uses cmd-lets for the majority of it’s work.  It also allows you to call WMI and .NET methods and functions.  So similarly to to VBScript we can instantiate them for additional functionality.  The possibilities are plain astounding, and well beyond scope for this blog post.

Output usage:

Same as with our previous entries, all the work done is aimed at getting an appropriate output.  As such, capturing, reading, and modifying the output of all our commands is important.  How PowerShell does this, is similar to both VBScript and BASH.  You can pipe for output as input for other commands as well allowing for some very elegant one liner solutions.

Since PowerShell keeps everything in the pipe an object, your variables become immensely powerful since they are more than just stored returns from commands, but instead stored objects with invoke able methods, or property lookup, etc.  Lets evaluate how this works quickly:

$SET = (Get-Service $SERVICE -ComputerName $TARGET)

Here we are using a Get-Service Cmd-Let to grab a specific service, on a specific machine.  Now the variable service we are grabbing will have certain native METHODS and PROPERTIES we can leverage to either invoke or pull data.  What we need to understand is that the $SET variable is now an Object, not a Value.

$SET.Start()

Start and Stop are two methods belonging to Win32_Service objects.  So what we are doing here is saying essentially:

$SERVICE object, Invoke Method Start

So everything you see at the above link is now callable, or retrievable through this variable.  Pretty cool huh?  One thing of note, if you invoke a method that changes the state of an object, it’s properties will not be refreshed until it is called again.  You will notice in the code I declare a $STATUS variable.

$STATUS = (Get-Service $SERVICE.ToUpper() -ComputerName $TARGET.ToUpper()).Status

Because the state is changed, the property Status is changed.  Hence I couldn’t just recycle $SET with the $SET.Status.  The object needs to be refreshed. (Don’t be confused by the .ToUpper(), these are just native PowerShell methods for string management.  In this case UpperCase)

Now the $STATUS variable could just be considered a value.  Since it is in fact a capture of the property value.  Starting to make better sense now?

I don’t really utilize any direct error handling inside this script outside of the $erroractionpreference value.

$ErrorActionPreference = "silentlycontinue"

This just means if I hit an error, move on to the next statement, and do so without any visible output to the user.

Conditional logic:

We’ve discussed if statements before, and as promised, I offer you a practical case statement:

SWITCH($STATE){
"Start"
{$SET.Start()
Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Starting"}
"Stop"
{$SET.Stop()
Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Stopping"}
"Restart"
{$SET.Stop()
Start-Sleep -Seconds 1
$SET.Start()
Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Restarting"}
}

The syntax says switch, but the methodology is the same.  All that is happening is we are saying if $STATE = “Start” or “Stop” or “Restart” do this.  Either 3 separate if statements, or 1 case statement.  Fairly efficient manner for handling complex conditions that hinge around one variable.

So what does this PowerShell script do?

It begins by declaring 3 variables that are to be string values.

Then it tells the shell to remain silent and continue on errors.

Next it builds a function to be reused through the remainder of the script

Function Set-RemoteService{

This is where our case statement is built and stored.  We also have an additional conditional statement in there to insure that one of the 3 pre-defined strings were typed correctly.  Now we perform an if statement to determine that the variables were defined at execution, if not then we prompt the user for input.  If $TARGET is empty we simply define it as $ENV:COMPUTERNAME, which is equivalent to LOCALHOST (just a bit more elegant).

Now it performs a quick check to determine if the $TARGET passed was a file or simply a name.  If it was a file, the file is opened and assigned to the $READ variable where a for each statement is run against it.

A $PING test is done using a WQL to determine if the machine is online, or if the name is even valid.  (the reason we do this, is that it’s increasingly faster to ping for response than to wait for a timeout to occur on a RPC request).

If the machine successfully pings it moves on to declare the $SET variable object for use in the Set-RemoteService function.  Then it executes the function, then a 1 second pause, and then builds an object property variable that is used in a console message to the user notifying them the state of the service after the action.

If it wasn’t a file, it does the same thing without a ForEach statement.

}


So you want a little more?

I’m going to quickly show you how you can integrate this script, into a profile function.  It’s exceptionally easy, and I recommend it for scripts you’ve built that you find invaluable for day to day use.

Here goes the modified code:

Function Set-RemoteService {
PARAM([string]$STATE,[string]$SERVICE,[string]$TARGET)
$ErrorActionPreference = "silentlycontinue"
Function SRS{
SWITCH($STATE){
                       "Start"
                           {$SET.Start()
                                  Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Starting`n"}
                       "Stop"
                           {$SET.Stop()
                                  Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Stopping`n"}
                       "Restart"
                           {$SET.Stop()
                     Start-Sleep -Seconds 1
                            $SET.Start()
                                  Write-Output "`nService: $($SERVICE.ToUpper()) `nStatus: Restarting`n"}
                           }

if ($STATE -ne "Start"){if ($STATE -ne "Stop"){if ($STATE -ne "Restart"){
      Write-Output = "Invalid service state entered`n";Break}}}

                          }
if ($SERVICE -eq "")
    {$SERVICE = Read-Host "Please enter a valid service name"}
    if ($STATE -eq "")
        {$STATE = Read-Host "You need a valid state entry. Start, Stop, or Restart"}
       if ($TARGET -eq "")
           {$TARGET = $ENV:COMPUTERNAME
      $FILECHECK = Test-Path $TARGET
             if ($FILECHECK -eq $TRUE){
                 $READ = gc $TARGET
                           ForEach ($ITEM in $READ) {
           $PING = gwmi -Query "select * from Win32_PingStatus where Address=’$ITEM’"
    if ($PING.StatusCode -ne 0)
       {Write-Output “$ITEM not found online, or invalid hostname.`n"}
   else
       {$SET = (Get-Service $SERVICE -ComputerName $ITEM)
                              SRS
                          Start-Sleep -Seconds 1
$STATUS = (Get-Service $SERVICE.ToUpper() -ComputerName $ITEM.ToUpper()).Status
             Write-Output = "Machine: $ITEM`nService: $SERVICE`nStatus: $STATUS`n"
}
                                                       }
                                               }
             else
                   {$PING = gwmi -Query "select * from Win32_PingStatus where Address=’$TARGET’"
         if ($PING.StatusCode -ne 0)
              {Write-Output "$TARGET not found online, or invalid hostname.";
                                                  break}
                    $SET = (Get-Service $SERVICE -ComputerName $TARGET)
                                   SRS
                        Start-Sleep -Seconds 1

$STATUS = (Get-Service $SERVICE.ToUpper() -ComputerName $TARGET.ToUpper()).Status
             Write-Output = "Machine: $TARGET`nService: $SERVICE`nStatus: $STATUS`n"
}

}

We’ve enclosed the entire thing into a Function statement now, and redefined the internal function.  The reason for this was, well, I prefer to stick with cmd-let naming patterns.  I’d rather call set-remoteservice at the command line.

Now, we need to build our profile, from inside PowerShell, at the command line type:

notepad.exe $PROFILE

Now paste the above code and save it.  If you are inside ISE press F5 to run the script and update your profile, or if you are in the standard shell type:

powershell

This will start a new shell instance inside your previous shell with the updated profile.

Be mindful, PowerShell and PowerShell ISE have two separate profiles.  So if you perform this in ISE the standard shell won’t receive the update and vice versa.


That does it for my stick to the script series.  I hope you’ve learned a lot regarding scripts through this and it’s shed some light onto what scripting is and de-mystified it for you.

I will continue to post scripts for various things over time, as well as neat tricks as I learn them so I wouldn’t say this is truly the end. 

Editors:

If you intend to do a lot of scripting, I recommend you grab yourself a steady editor.  Preferably one with configurable highlighters.  Below are a list of a few free and premium editors I recommend.  I’ll start with the freebies:

  • Context – A nice, windows based, free editor (my usual windows editor). Configurable highlighters, comparison tool, and configurable execution keys.  All the basic needs of a script writer.  Minus integrated libraries.  I believe development has all but stopped since 09, so this might not be the best editor to stick to.
  • Emacs – Extremely powerful development environment built for OSS, but with Windows versions.  This is more than a simple editor, it’s a Swiss army knife.  It will require a bit of a learning curve to begin using it but is worth it in the end.
  • VIM – My preferred editor in Linux, switchable modes, highlighters, line numbers, split views, compares, command line style editing.  Like emacs, it’s extremely powerful, and has a bit of a learning curve.  I prefer VIM, but Emacs is far more extensible, just fyi.
  • NotePad++ – A really nice OSS editor for Windows.  I should probably start using this over Context, but I’ve already spent so much time with Context configuring it, I’ve become extremely comfortable with it.  I’d recommend this editor to any novice windows scripter.
  • PowerGUI – Both a free, and paid edition.  I recommend this editor for PowerShell whole heartedly because of it’s syntax and object recognition tools.  If you are going to do a lot of work with PowerShell, I recommend using this editor for it.
  • PSEdit – Included with the PowerShell ISE.  It’s the standard PowerShell editor.

Premium:

If you are interested in learning more:

Stick to the Script Parts 1 – 4:

  1. Stick to the Script…
  2. Stick to the script #!/BIN/BASH…
  3. Stick to the script CreateObject(“Wscript.Shell”)…
  4. Stick to the Script PS C:\> PowerShell …

Today we are going to examine VBScript. Now, a bit of fair warning before we start on this.  This scripting language is a departure from our standard shell scripting.  VBScript is an automation scripting language for the Windows Script Host (WSH)/Internet Explorer (IE)/Internet Information Services (IIS), in short, this is the primary Windows scripting language.

Not to get too carried away on the subject, but this language uses a Component Object Model that allows direct (or indirect) action on just about every object within the windows environment and does so natively via the windows script host interpreter ((which is extensible) (wscript.exe or cscript.exe)).  There are nuances to that statement, and I encourage you to investigate it more thoroughly, however I’m going to jump right into some of the basics of using this language, then discuss the code below as we’ve done in the previous 2 posts.

First off, what is a component object model?  Put as simply as possible, they are exposed objects that you can manipulate in the environment by invoking their methods and properties.  This scripting language brings you dangerously close to windows development at times, but at it’s heart, it’s still very much a script.  Controlling environment objects and properties, performing sequenced tasks, and no compile.

Now, I’m going to introduce 4 new things with this script, I will name them now and explain them after we’ve reviewed the code:

Arrays

WMI

Subroutines & Functions (They can be the same, but in WSH they are different)

Loops

Function VBscript (blog)

The overall activity of this script is the same as our drivemounter batch script we made.  Lets take a look at the code.


‘||========================================||
‘||Author Daniel Belcher                   ||
‘||Function drivemounter                   ||
‘||Date 5/4/2011                           ||
‘||========================================||
‘|Objects, Variables, and Arrays ***********
‘\==========================================/
‘|Set number of items in array             ‘|
‘|Example: 3 drives would be (3,1) and 3   ‘|

Dim ArrDrive(6,1)                          ‘|
DriveCount = 7                             ‘|
’|Define the Array to read                 ‘|
    arrDrive(0,0) = "h:"                   ‘|
    arrDrive(0,1) = “\\uncpath\1”          ‘|
    arrDrive(1,0) = "i:"                  
‘|
    arrDrive(1,1) = ”\\uncpath\2”          ‘|
    arrDrive(2,0) = "j:"                   ‘|
    arrDrive(2,1) = ”\\uncpath\3”          ‘|
    arrDrive(3,0) = "k:"                   ‘|
    arrDrive(3,1) = ”\\uncpath\4”          ‘|
    arrDrive(4,0) = "l:"                  
‘|
    arrDrive(4,1) = ”\\uncpath\5”          ‘|
    arrDrive(5,0) = "m:"                   ‘|
    arrDrive(5,1) = ”\\uncpath\6”         
‘|
    arrDrive(6,0) = "n:"                   ‘|
    arrDrive(6,1) = "\\uncpath\7"          ‘|
‘|Define the Server to Ping for Net Check  ‘|
Dim
strSvr                                 ‘|
    strSvr = "servertoping"               
‘|
‘==========================================================================

Const DEBUGMSG = True    ‘True = Debug Messages, False = No Messages
Dim oWShell
    Set oWShell = CreateObject("WScript.Shell")
Dim oWNet
    Set oWNet = CreateObject("WScript.Network")
Dim Target
    Target = "."
Dim oWMISvc
    Set oWMISvc = GetObject _
("winmgmts:{impersonationLevel=impersonate}!\\"&Target&"\root\cimv2")
Dim oPing
    Set oPing = oWMISvc.ExecQuery _
("Select StatusCode From Win32_PingStatus where Address = ‘"&strSvr&"’")

‘|Main Run =================================================================
DriveMount        ‘Perform Function DriveMount
Report            ‘Perform Function Report
Wscript.Quit(0)   ‘Close Script
‘SubRoutines and Procedures ================================================
Sub Mount(letter,path)
    oWNet.MapNetworkDrive letter,path
End Sub
‘*****************************************************
Sub Umount(letter)
    oWNet.RemoveNetworkDrive letter
End Sub
‘|Functions ================================================================
Function DriveMount
http://msdn.microsoft.com/en-us/library/aa394350(v=vs.85).aspx
    For Each item in oPing
        If IsNull(item.StatusCode) or item.StatusCode<>0 Then
‘**********Domain not reachable, unmount drives in arrDrive
        x = 0
          do until x = DriveCount
             On Error Resume Next
                Umount arrDrive(x,0)
                       If debugmsg then msgbox "Umount"& vbcrlf _
                       &"Drive Letter: "&arrDrive(x,0)& vbcrlf _
                       &     "Error Number: "&Err.Number& vbcrlf _
                       &     "Error: "&Err.Description
              Err.Clear
        x = x + 1
          loop
        else
‘**********Domain exists, mount drives from arrDrive

        x = 0
          do until x = DriveCount
             On Error Resume Next
                Mount arrDrive(x,0), arrDrive(x,1) 
                       If debugmsg then msgbox "Mount"& vbcrlf _
                       &"Drive Letter: "&arrDrive(x,0)& vbcrlf _
                       &     "Error Number: "&Err.Number& vbcrlf _
                       &     "Error: "&Err.Description
              Err.Clear
         x = x + 1
          loop 

        End if
   Next
End Function

‘*****************************************************
Function Report
      If Debugmsg then msgbox "Script completed"
End Function
‘End ======================================================================


Copy and paste the code and save as a .vbs file (unless you have a specific editor, I recommend notepad to insure formatting is properly translated.)

Lets start with our 4 areas of interest we’ve focused on with the previous posts.

  1. Sequence
  2. Command usage
  3. Output usage
  4. Conditional logic

Sequence:

Sequencing and Command usage will be the most interesting topics this time around.  Unless wscript is pointed towards a specific function or sub, it reads everything in line.  This point is rather large when you consider BASH and POWERSHELL and a lot of other script languages which will error out if something is called that hasn’t been previously declared (speaking about functions and subroutines, variables must always be declared before usage).  However, the rule remains that everything is read one after another, or AS the interpreter is directed to read.

As a habit when I write my vbscripts I will almost always write them in this format. Variables, Run sequence, subs, functions.  There is no real reason for this outside of the fact I prefer to immediately see variables and prescribed run order.

Now lets look at our variable declaration and focus on what’s new.  An array, and a multidimensional one at that (more than two columns of data).  An array is an assortment of data stored in memory, think of it like a spreadsheet.  Row 0,1,2,3,4,5,6 and Column 0,1,2,3,4,5,6.  This would be a multidimensional array.  A simple array would be 1 Column (column 0) and however many rows as you wanted.  In this script I declare this array to be limited to 7 rows and 2 columns.  That sounds weird right, cause the declare clearly says (6,1) ?  Remember that 0 counts so 6 = 7 and 1 = 2, but you declare it by the integer value of the last entry.

Dim ArrDrive(6,1)

I then declare my Array entries by defining ArrayName(row,column) = data

arrDrive(0,0) = "h:"          ‘|
arrDrive(0,1) = “\\uncpath\1” ‘|
arrDrive(1,0) = "i:"         
‘|
arrDrive(1,1) = ”\\uncpath\2” ‘|

From here on out when I need to utilize any of these pieces of data I can simply call them using their array name and position.  Another way to think of this is a shared variable name perhaps?

Now lets discuss our run sequence:

‘|Main Run =================================================================
DriveMount      ‘Perform Function DriveMount
Report          ‘Perform Function Report
Wscript.Quit(0) ‘Close Script

This block of code dictates the run order of my script.  The first 2 are Functions I have defined, and the last is a wscript method to insure a clean exit (more on this in command usage).

Sequence is important. For organizations sake, I will take a quick and easy VBScript and build it in this fashion.  For me, and for others, it allows for simpler review and additions to be made to the script that could and will improve it’s functionality.  It’s also far simpler to isolate bad sections of your script and to target a specific function for testing.

Now let’s discuss the iteration statements that are introduced in the DriveMount function.  I will touch on the code more specifically when we break down what the script is doing, but an iteration statement is just a loop that continually performs a task until a certain condition has been met.  That could be reaching a certain number, that could be reaching the end of a file, that could be reaching the end of an array, etc.  In this script (although not how I would naturally do it) I’ve offered 2 concrete examples of vbscript loop statements for your review.

Finally, subroutines and functions.  Similar to labels, identical to the function I showed with BASH.  They are, put succinctly, sections of code that can be called by name to perform a fixed task.  In VBScript specifically a sub routine passes back no data, a function does.  So if I need to perform mathematical operations and reuse it as a variable elsewhere, I’d use a function*.  If I just need to move a file from one location to the next, a sub.  As a safe bet inside of VBScript, declare them all as functions, there is really no benefit I’ve found one way or the other.  I declare them as Sub and Function only to offer them as an example. Syntax for this is:

 Function Name                              Sub Mount                         

        Code to use                             Code to use

End Function                                End Sub

If you wish to use arguments with these functions/subs you would use:

Function Name (arguement1, arguement2, etc)

                Code using arguments provided

End Function

Command Usage:

Unlike our other scripting languages so far, the WSH doesn’t really utilize commands per say.  Command usage is actually called through a COM.  So when you build a vbscript, you spend the first portion of the script defining what COMs will be utilized in the script.  This is all part of my initial variable declaration (DIM statements or Declare Into Memory)

Dim oWNet
    Set oWNet = CreateObject("WScript.Network")

This is essentially creating the callable object or “commandoWNet for me to use moving forward.  What’s actually happening is I am using the WScript object to instantiate (create an instance of) the WshNetwork object for later method or property usage (exposing the object for manipulation).

This is how an object based language works, call the object, use the object, (and ideally) release the object.  When I later use these objects methods I will do so by invoking them.  Syntax comes in to play in these cases.

             oWNet.MapNetworkDrive letter,path

That is the invocation of the WshNetwork MapNetworkDrive method which requires drive letter then path to map.  Hopefully that makes sense, it’s an extra step but allows for some amazing control and it remains rather light weight as it depends on existing objects for it’s control base as opposed to having them already built in.  However that’s not to say there aren’t commands built into the WSH but they are for conditional and iteration statements as well as integer, string, and date functions (w3schools.com has a nice breakdown as well) etc.

Output Usage and Conditional Logic:

For this script the output usage is in reading a ping response, mapping drives, and in exception handling (error catching).  We read the status code from the WMI Win32_PingStatus class object (list of WMI Classes) then use it as our condition.  We then loop through the array and perform the appropriate action while catching and clearing any potential errors.  We also redirect that error output to a popup message if DEBUGMSG Boolean is set to TRUE in our initial variable declaration.

Our conditional logic as stated before is in the form of IF statements.  I will demonstrate case statements in my PowerShell write up.  Case statements look for something (usually a specific string type) and respond in a defined way.  However a case statement is strictly defined where an IF statement is generally this or that, it will make more sense in the future I promise.


So what does this vbscript do?

In this script we open with our declarations.  We define our Array ArrDrive as a 7 row and 2 column array (0 counts, remember though declaration will be the last integer value so in this case: 6,1) and supply it with values.  We set a Boolean for debugging messages and we set our object instance for WScript.Network to oWNet. 

We also start an instance of the WMI Service, and define a WQL that reads the ping status to a targeted server (strSvr) for us (Target=”.” means the local machine, it can be changed for remote PC name, more on wmi scripting).  (WMI is Windows Management Instrumentation, similar to the WSH objects it contains a series of namespaces, classes, methods, and properties to control the windows platform.  Essentially if you can do it on the Windows machine, there is a WMI namespace/class/method involved in it.)

Dim Target
    Target = "."
Dim oWMISvc
    Set oWMISvc = GetObject _
("winmgmts:{impersonationLevel=impersonate}!\\"&Target&"\root\cimv2")
Dim oPing
    Set oPing = oWMISvc.ExecQuery _
("Select StatusCode From Win32_PingStatus where Address = ‘"&strSvr&"’")

Now that we have all that declared we move into our run sequence.  The Function DriveMount is first and it immediately performs a For Each statement through our oPing WQL and grabs the statuscode value and compares it with an IF statement.

For Each item in oPing
         If IsNull(item.StatusCode) or item.StatusCode<>0 Then

If there is no value, or the status code is anything but 0 (0 is a clean exit code, success, that’s what the status code is in its simplest form) then we aren’t in our appropriate network so we will begin a Dismount loop.  If the return value is 0 and is NOT null then we will perform a Mount loop.

Lets stop here for a second so I can explain this loop line for line, first the code sample:

‘**********Domain exists, mount drives from arrDrive

x = 0
  do until x = DriveCount
     On Error Resume Next
        Mount arrDrive(x,0)
, arrDrive(x,1)  
              If debugmsg then msgbox "Mount"& vbcrlf _
              &"Drive Letter: "&arrDrive(x,0)& vbcrlf _
              & "Error Number: "&Err.Number& vbcrlf _
              & "Error: "&Err.Description
     Err.Clear
x = x + 1
   loop

We declare x as 0, we will enumerate this integer value with each pass of our iteration (loop).  Word of warning here, if using this method make doubly sure that you set your enumerate value outside of the loop or it will be re-declared each loop and become an infinite loop.

We do until x = DriveCount, we declared DriveCount as 7 at the start of the script.  This loop will loop 7 times.

On Error Resume Next, we will not let error outputs break our function, we will perform the next in line.

We run the Mount subroutine passing the letter and path arguments which we pull from our arrDrive array.  We use x to select our row (remember we are enumerating x) and select the appropriate column for the data to pass.

Now as an extra step, we check for the DEBUGMSG Boolean, if it’s TRUE, we generate a popup window that tells us what Subroutine is being used, drive letter, error number, and error message. (if the drives are already mounted it WILL error, the error is that the drives are already mounted, so this is helpful if we are chasing something down)

We clear the errors that may or may not have been generated.

Now we take x and we add 1 to it, so 0 becomes 1, 1 becomes 2, 2 becomes 3, etc on each successive pass.

Finally we terminate the statement with loop so that the WSH knows to return to the start of the loop statement.

Easy huh?  There is a lot of power to learning how to use loops and for each statements effectively.

I would normally have just made another for each statement against the array which would read it to the end, but I felt this serves as a great learning opportunity.

At this point our DriveMount function is complete so we move to the next Function in the run sequence which is Report.  Report runs a debugmsg check and tells us the script completed if debugmsg is true.

Now back to our run sequence where we close our script with

Wscript.Quit(0)

This insures an exit code of 0 which is a universal “everything ran great, nothing to see here” output.

End Function

Next up we will be discussing PowerShell.  A very robust shell scripting language for the newer windows platforms (xp sp3+) that utilizes commands, .net, com, wmi, dcom, and the list goes on.  I’ll try to keep it lite.


Now for some bonus material, lets take a look at the Mount loop if I had used a For Each instead of a conditional loop:

x = 0
For Each object in arrDrive
    On Error Resume Next
       Mount arrDrive(x,0), arrDrive(x,1)
            If debugmsg then msgbox "Mount"& vbcrlf _
            &"Drive Letter: "&arrDrive(x,0)& vbcrlf _
            & "Error Number: "&Err.Number& vbcrlf _
            & "Error: "&Err.Description
     Err.Clear
x = x + 1 
Next

You are probably thinking “this looks the same, why is this better?”.

When you perform a conditional loop statement it needs a stop point which is another factor you have to manage.  In this case we had to declare a maximum loop count (our DriveCount variable which was 7).  A for each statement will go through a list till the end and then move on.  You can think of a for each statement as “For Each item in whatever until the end.”

If you have any questions or corrections, please leave a comment and I’ll try and get back with you about it. For an unaltered version of this script to copy and paste, go here.

Alright, so continuing our discussion on scripting with Bash scripting in Unix and Linux.  In the same way that command interprets a script to perform actions in order so does bash.  What’s the difference then?  The sheer volume or tools available to script and manipulate the environment is greatly increased.  The manageability of output, the variety of conditionals, arrays, and integration of the shell into the environment.

It’s worth mentioning up front, I could spend days on the subject of any of these scripting languages.  However for the sake of space and interest, I will still keep things high level.  It’s ALSO worth mentioning that Bash/shell scripting is something of a nix requirement.  Not that you have to know shell scripting to use the OS, but it’s certainly a primary part of the culture.  Bash scripting also works within OSX environments (since it’s built on BSD), however some research and testing will be required to find the similarities between their nix and others which leads to my next topic…

Linux and Unix {

If you are reading this, I will assume you know what Linux and Unix are.  If not, here’s some reading material for Unix and Linux.  What you may or may not be aware of is that although there is a great level of standardization as maintained by IEEE POSIX standards, there is also a lot of distribution specific tools or practices.  In other words, a person can generally port work from one distribution to another, but should never assume a one size fits all solution across the board.  Which is precisely why there are so many distributions, because one size does not fit all, at least not to everyone’s tastes.

I also want to take a moment to point out that this is not a flaw. Although standardization is a good thing, limited options are not. There are other standards that GPL licensed software (generally linux)adheres to that gives it reliable standards or compliance. They also maintain partial POSIX compliance as well. It’s an interesting subject, and the differences are fairly minor in most cases. Do not be scared by the options, determine what it is you want and go from there if you are interested in trying.

Now a days the user experience one has come to expect from Windows or OSX is easily found with a popular Linux distribution (Ubuntu and Fedora immediately come to mind).

     Bash Scripting {

Now lets take a look at a very simple script that I’ve briefly modified from Mendel Cooper’s guide for an example on here:


#!/bin/bash#Log Cleanup
#Author: Daniel Belcher
#4/30/11
#Built for tutorial purposes
#Idea for script from beginner example
#in advanced bash scripting guide:
#http://tldp.org/LDP/abs/html/sha-bang.html
#Variable declaration
LDIR=“/var/log/”
DATE=`date ‘+%m%d%y’`
RUID=0
E_USER=
1#Checking for root user, exiting with error code 1 if not root
if [ “$UID” ne “$RUID” ]
then
          echo
“User needs to be root”
exit $E_USER
fi
#Copying the current messages and wtmp logs to an archive with date code
cp $LDIR“messages” $LDIR“messages.”$DATE;
     cp $LDIR“wtmp” $LDIR“wtmp.”$DATE;
#Taking the last 10 lines from that log and writing it to the current
#messages and wtmp to reset them.
tail n 10 $LDIR“messages.”$DATE > $LDIR“messages”
          tail n 10 $LDIR“wtmp.”$DATE > $LDIR“wtmp”               
   sleep
.5
#Finding all messages and wtmp files with an
#access time of 7 days or more and deleting them.
find $LDIR -iname messages.\* atime 7 exec rm {} \; #could also swap –exec rm etc for –delete 
             find $LDIR -iname wtmp.\* atime 7 | xarg rm

Save as a .sh

then

chmod +x scriptname.sh

Now same as with our batch script, lets focus on 4 things.  Hopefully some similarities begin to present themselves.

  1. Sequence
  2. Command usage
  3. Output usage
  4. Conditional logic

Sequence:

Now similarly to the command shell, Bash reads the script line for line.  However instead of GOTO statements in Bash, we would actually build functions. (I do not have an example in this script but the syntax is generally name_of_function() { work to perform }.  I’ll discuss this more with PowerShell and VB script).  All lines are read as if they were typed at the console.  Here you will see I also do something that we didn’t do within the batch file.  Variable declaration (LDIR=”/var/log/”) then I can call it again in the script as $LDIR.

This allows you to take a commonly used command, object, integer, or string and set it to something easily repeatable inside the script.  Variables can be declared at run time, as an argument, or produced from script output to name a few methods for declaration.  They are integral to programming in general.  As far as sequence is concerned, as long as the variable is declared before it’s called you are fine.

Command usage and Output usage:

Now hopefully you are seeing that the commands available in a shell are what does most of the heavy lifting in terms of work.  Something worth learning in command usage is piping.  Piping is taking the output of one command and using it as the input for another (to put it simply).  A visual I’ve always liked to use in understanding what is happening is this:

A lake brings water to your home.  A pipe exists between the lake and your faucet (very simple). On the course to your house a series of filters are applied to weed out what you don’t want from the water by the time it reaches your house.  In this case the lake is the source or original input, and the filters are the series of commands applied to the pipe before the final output at your faucet.

So in pseudo code: lake-water | filter-fish | filter-algae | filter-dirt > glass-of-water.drk

The end result is that water is transported through that pipe and all unwanted elements are removed until we finally have our desired glass of water to drink.

In this case we use:

find $LDIR -iname wtmp.\* atime 7 | xarg rm

This will take the findings from the FIND command and pass it to xarg which initiates rm and deletes these files.

Hopefully this is beginning to paint a clearer picture of pipe and command usage.  Shells that allow for command piping are a God send and allow for some very elegant solutions to complex problems in simple one liners that a user could easily alias, but that’s another subject entirely.

Conditional Logic:

Now in an identical fashion to our batch file we are performing an IF statement.  IF statements are one of, if not the most, commonly used conditional statements.  Notice there is a very distinct difference in the syntax used here in comparison to the one we used in our batch.  The testing method and outcome are still the same however, so do not be deceived.  IF statements put simply are: if it’s this then do that.  Since conditional tests are capable of greater complexity (and will be with all other script engines we discuss going forward) it needs to be terminated so the script knows when the conditions or work statements are final. We close this IF statement in Bash script by spelling IF backwards FI.

The same is true for all conditionals within bash.  They will open spelled forward and close in reverse.  Elif … file case … esac; when you begin to learn what and how logic is applied in scripts you will know how to appropriately apply the syntax.

So what does this Bash script do?

The script opens with a shabang! (#!/bin/bash) which tells the shell that it’s a script to be interpreted by the bash shell found in the bin folder.

Next it declares 4 variables, LDIR (log directory path), DATE (current date no special characters month date year), RUID (Root user id), and E_USER (error code to use if the user fails our user check).

Now we check a shell variable $UID (current user’s ID #) and see if it’s different than the $RUID (-ne = not equal). If it’s not the root user we exit with the error code # assigned to $E_USER.

If we pass that check we will now copy the messages and wtmp files under the /var/log/ folder to their same folder as filename.currentdate.  Example: messages.050111

Now we take the last 10 lines of that file and output it to the primary log files messages and wtmp effectively overwriting the previous data and truncating that file.  > operator on the command line is a redirect (as we saw with our > nul in the batch file).

Finally we take and search that folder for all files following the archived file syntax and search for an access time greater than 7 days then we delete them.  I’ve used two different formats for deletion to show the different possible ways of doing this.

     } gnitidE shaB

} xinU dna xuniL

In my next post I’m going to break into vb scripting, which is a considerable departure from the shell relative scripting we’ve looked at so far.  However I will return to another form of shell scripting with PowerShell, stay tuned.


Just for another example, how would this script look utilizing a function?

#!/bin/bash#Log Cleanup
#Author: Daniel Belcher
#5/1/11
#Variable declaration
LDIR=“/var/log/”
DATE=`date ‘+%m%d%y’`
RUID=0
E_USER=
1#Checking for root user, exiting with error code 1 if not root
if [ “$UID” ne “$RUID” ]
then
         echo
“User needs to be root”
exit $E_USER
fi
#Function to create backup, truncate, and purge old log files.
cleanup () {
                 cp $LDIR$1 $LDIR$1“.”$DATE;
                         tail n 10 $LDIR$1“.”$DATE > $LDIR$1
                                 sleep .7
                         find $LDIR iname $1.\* –atime 7 exec rm {} \;
                 return 0
                 }

#Calling function cleanup () and passing 1 argument

cleanup messages
cleanup wtmp

exit 0

A lot cleaner.  Hopefully you can see the benefits of functions now, or even loops as we will examine in the future.

So I was in Tennessee on Sunday for Easter, and the conversation came up regarding scripting and programming. Now I’ve heard a lot of people discuss scripting with a sense of unapproachable grandeur and well, nothing could be further from the truth. Some scripting languages share objects/classes/methods etc. as your more robust programming languages, but quite honestly, it’s just a method for causing objects to behave in a sequenced manner according to your wishes. Not much different than a script an actor might read from.

Much like actors, who perform better for certain roles, script languages have better fits in certain areas than others. I intend for this to be a multipart post to discuss different script languages moving forward, but lets start with the basics. I’m going to break down 4 popular, widely used, scripting languages and show a few simple examples of them at work.

“Well, how is that different than a program?”

Well, the primary reason they are different is programming yields binary files. Essentially all the coding you do is then changed into unreadable machine code that performs what the developer intended.

Scripting is code that tells a binary file what to do. The binary file that reads the script is called an interpreter. So it will dynamically take code that is human readable and translate it to the computer.

Scripting is very versatile, but generally speaking you aren’t creating anything, only controlling what presently exists. More sophisticated shells (bash, PowerShell) will allow you to extend functionality of current commands etc., but generally speaking it’s still scripting unless it results in a new program all together.

In the end, the dynamic nature of scripting makes it a preferable solution for real time solutions, and automation in computer infrastructures. It’s also considerably easier to debug and resolve issues as there aren’t lengthy compile processes between build outs. However, there are times when a script just won’t cut it and you need to build an application.

Batch Editing {

Let’s start with one that a lot of people in the windows world are already very familiar with and start establishing an idea of what a script is.

With batch editing, the .bat extension identifies to the interpreter that the file is to be read as a script by the command shell. Anything you can do within the cmd prompt can be sequenced and completed in a batch file. It’s read sequentially line for line and accepts GOTO statements as well as IF clauses and LOOPs.

Let’s break down a simple batch file I use for drive mounting and dismounting at login:


@echo off
Rem —————————————————
Rem Drive Mounter
Rem Author: Daniel Belcher
Rem Modify drive letters and paths to match need
Rem Place in Startup folder for login init
Rem —————————————————

Rem Check for Corporate connection
ping -n 1 addresstoping > nul
If %errorlevel% EQU 0 (GOTO MOUNT) Else GOTO UMOUNT

Rem Drive Mounter
Rem Modify this section to match your desired paths

:MOUNT
net use /p:yes > nul
net use h: \\UNC\PATH\ONE > nul
net use i: \\UNC\PATH\TWO > nul
GOTO END

Rem Drive Umounter
Rem Modify this section to match paths for Umount
:UMOUNT
net use /p:no > nul
net use /d h: > nul
net use /d i: > nul
GOTO END

:END


I just want to focus on 4 things here.

  1. Sequence
  2. Command usage
  3. Output usage
  4. Conditional logic

Sequence:

As a batch file is read by the command shell its read line, by line. This is equivalent to someone typing these commands one after another. If you wish to jump around in the script you can use GOTO or RETURN.

The commands “net” and “ping” should be familiar commands to any windows administrator. They are all part of the native shells command base. This is fairly standard in any shell scripting which is more akin to controlling commands than objects.

As with all scripts there is an output to be utilized. When you are building a sequence it’s important to check for a return and respond accordingly. An example of output capture here is done with %errorlevel% which is a standard output variable with the command shell. It’s also possible to declare variables inside a batch file using SET VARIABLE = STRING/ACTION/STATEMENT (that’s a bit beyond scope for what I’m covering here). You will also notice I tail most statements with a > nul. What’s being done here is called a redirect, that is, redirecting the output to a file or another command, in this case a null value which causes no console output.

Finally conditional statements. As it’s name implies, it’s a statement that checks for a certain condition. In this short script we are checking if the value of our %errorlevel% variable is equal to 0. This is defined by the conditional IF. So what we are asking the script here is, if ping has no errors GOTO MOUNT, else GOTO UMOUNT.

So what does this batch file do?

It calls the command shell, requests that it pings addresstoping 1 time and if there is no errors (destination is reachable) it goes to mount the network drives to the local machine. If the host is unreachable it clears potential instances of those drive mappings and then closes out.

If you want more information on batch commands and batch in general, read up.

} gnitidE hctaB

 

In my next post(s) I will give some examples of Bash scripting, PS scripting, and VB scripting.  I will also dedicate more focus towards them and try to explain their strong points and usages. 

So recently we had to perform an AD cleanup of aged computer objects in our environment.  For a lot of companies this is a pretty standard procedure, for others, it’s just ignored (as was the case for us for a while).

Anyway, in the course of completing this task we had looked at a few scripts and tools that others had written/published around the internet and I inevitably got the bright idea to sit and write a tool for us to use in PowerShell.  So I setup my goals and fired up the old editor and went to work.


Objectives:

  • Identify aged objects
    • To identify aged objects, I went with the PasswordLastSet property of the object. There really isn’t a much better one to utilize, and it works off a 30 day cycle so it’s close enough.
  • Write out data for records
    • For write out I built a switch (–report) that will utilize the export-csv cmdlet to export a record of machines to the current users desktop.
  • Move objects to different OU
    • For moving objects I used the Move-ADObject cmdlet from the activedirectory module available with win 7 and server 2008 features.
  • Generic (no non standard add ins)
    • To keep it generic I chose to use the activedirectory module since we are running on 2008 servers, and migrating to windows 7. This insures future state usage, and prevents additional downloads or installs for functionality.
  • Modular (sometimes you just want a report)
    • To keep it modular, I built the (-move) switch. Unless the user explicitly defines this then the script will only report what was requested. Either to console output, or to a csv on the desktop (-report).
  • Versatile for targeting object date ranges and OUs
    • To insure versatility, I left 3 variables to be declared. $TIME, $TARGETR, $TARGETM. Date range, target OU to read, and Target OU to move respectively.
  • Script initially with potential to integrate in profile as a function
    • To build it as a standalone script, I originally had a (-help) switch that would output similar information as the get-help blah-blah –examples. Now with it functionized, I’ve removed that and added an overall function name to the code.

How it works:

Once applied to the PowerShell profile it can be called using get-adinactivecomputer.  By default it requires a date range either specified in <number>d for days or <number>y for years and a target OU to search from.  If these variables aren’t applied at the command line the function will request them at run time.

If –move and –report are specified then the function will need a target OU to move the items to and will generate a list with Object names and PasswordLastSet dates on the desktop and attempt to move them to the specified OU.  The switches must be declared at the command line but all variables are requested at run time if not stated before hand.


Now the nuts and bolts…..

admove1

First we check for the $TIME variable and request if it’s not present, then insure it’s in string format.  We’ll check that string for a d or a y to determine how to handle the string and then convert it to an appropriate integer value.  We’ll also bailout if we can’t distinguish the range (since this is a has the potential of causing serious AD harm we want to insure the user is accurate in their input).

Second we’ll check for the –report switch and if it’s present we’ll run the internal ADCLEANUP function and pipe it to export-csv $home\desktop\inactivecomputers.csv.  If it’s not then we’ll just generate output to the console.  We pass the $TIME and $TARGETR variable to this function.

Third we check for the –move switch, and if it’s present run the internal function MOVEOLD and pass it the $TARGETM variable.

Fourth and final step, we nullify a global variable set during the ADCLEANUP function.

 


admove2

First step of the ADCLEANUP function is to insure the activedirectory module is loaded.  Next it verifies that a $TARGETR variable exists, if not it will prompt for one.  It then will take and search AD for the distinguished name of the OU provided. 

Next we build a date range variable using .Net Date Math.  We will subtract the total number of days by the integer value provided in $TIME from Today’s date and store that in $COMPARE. 

Now we declare a global variable $ADLIST that grabs all computer objects with PasswordLastSet property less than or equal to $COMPARE from the $TARGETR OU.

We then process those objects with a ForEach into a PSObject for reporting purposes specifying the Name and PasswordLastSet properties.  I did this mainly to guarantee a clean csv export.

 


admove3

The MOVEOLD function is fairly straight forward.  We check for the $TARGETM variable, if it doesn’t exist we prompt for an input.  We then build $TARGETM into a manageable string, then again we set it as a proper OU.  We run the $ADLIST through another ForEach statement and use Move-ADObject to relocate each $ITEM to $TARGETM.

 


I posted this script on the script center repository.  I didn’t build in any error catching which I should have, and plan to revisit that at a later date.  I enjoyed writing it so much I wanted to take some time to dissect it and discuss it a bit.  Hopefully this information proves useful to someone with similar goals and or general interest. 

For the the Microsoft.Powershell_profile.ps1 and Microsoft.PowershellISE_Profile.ps1 version of the script, perform the following steps in order:

Uncomment lines 4 and 193

Delete lines 7 through 50

Delete: ,[switch]$HELP from line 6

Delete line 46

Modify line 60 to read:                    `n`nTry get-help Get-InactiveADComputer -Examples`n";break}