Skip navigation

Tag Archives: powershell

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.

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!

So as some of you may or may not know.  My wife is conducting a 365 picture project this year.  What’s a 365 picture project?  Essentially take a pic of you, or something in your life for each day of the year and compile it all together at the end of the year to sort of journal everything.  It’s, as you can imagine, a lot of pictures being taken a day.  I would say on average she has 5 to 10 pictures taken a day.  Realistically speaking, some days it’s about 2, others it’s near 30 or more. 

 

Ok, great, why are you telling me this? 

 

Well, I’m glad you asked.  She would spend a lot of time sorting, renaming, and organizing them.  I’ve been telling her a long time “just automate it” which is something I tell her about almost everything she does that’s repetitive.  Well finally after a long night or sorting through some 100+ photos she called me on it, and had me write her a script to rename all the files for her.  I also had a chance to get to show her a bit of that “computer stuff” I do everyday at work. Also, it’s been a while since I’ve done a scripting post, so I’m using this. 🙂

I figured I would go with powershell since I would have (easier) access to the .NET System.IO.DirectoryInfo class.  One of the things my wife loves is chronological accuracy, so pulling a timestamp from the image as part of its name seemed like a good idea.  She already had a routine where she copied them from her camera to an appropriate folder so specific file information was the only real concern here so this was going to be a very simple script.  Below is the code:

 


PARAM([string]$FOLDER)
$ErrorActionPreference = "silentlycontinue"
if($FOLDER -eq "")
    {$FOLDER = Read-Host "Path to picture folder?"}
    $LIST = Get-ChildItem "$FOLDER\*" -Exclude *ps1 `
    -Include *jpg,*tiff,*jpeg,*gif,*bmp,*txt
            $X = 0
    ForEach($OBJECT in $LIST)
        {$EXTENSION = $OBJECT.ToString().Split("\") | Select -Last 1
            $EXTENSION = $EXTENSION.Split(".") | Select-Object -Last 1
              $FILEINFO = New-Object System.IO.DirectoryInfo($OBJECT)
                $NAME = $FILEINFO.LastWriteTime.GetDateTimeFormats() `
                | Select-Object -Index 99
                    $NAME = "$($NAME) ($($X)).$($EXTENSION)"
                        Write-Output $NAME
            Rename-Item -Path "$OBJECT" $NAME
            $X = $X+1
            }       


So what’s happening?

First we run this script, either by launching it directly, or by launching it on a command line followed by the folder where our pictures reside. The folder path is our only variable here, if one isn’t entered at the start of execution, it will prompt the user for one.

Now the script will build an object list of everything inside the folder taking special care to exclude any potential powershell scripts in the directory to the $LIST variable.  I also built it to specifically include image file types (I didn’t want to rename some random non-picture files she might be storing in the directory (well, and txt for my testing purposes)). 

Now we define an integer value to $X so we can enumerate it for a counter during our ForEach loop, which we begin next.

For each file in $LIST we:

  1. Grab the extension for the file to variable $EXTENSION
  2. Retrieve the last write time of the file and store it as $NAME
  3. Set $NAME to $NAME + $X + $EXTENSION
  4. Write the final $NAME to console
  5. Rename the item to $NAME
  6. Enumerate $X by 1
  7. Loop

The outcome?

 

I showed her the way it renamed a series of text files I had placed in a test folder on my laptop.  At first, I received a rather dismissive “Oh, that’s good babe” however after I sat down at her desk and showed her the bad boy in action renaming another folder of 100 or so images in seconds, the sly grin found it’s way across her face.  The joy of automation.  The kind you can only get when you see something so small perform such a mind numbing laborious task for you.

She’s not quite ready to learn scripting, but at least now her eyes are open to other possibilities for automated solutions in her everyday computing.  And as a stay at home mother of 2, I’m more than willing to help her streamline all her recreational and productive time at the keyboard.  She is after all, my number 1 customer ;).

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 …

I’ve honestly been trying to post my final part to the “stick to the script” series.  However those actions have been soundly thwarted by an abundance of work, and a new workout regiment.  I hope to have it up and syndicated to myitforum before the weekend is over.

I’ve also built a nice sccm client install script, and thanks to John Marcum, I’ve built a nice remote cm wmi repair tool in powershell as well.

So stay tuned as I plan to start releasing those scripts after scrubbing them.

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}