An Unrivaled Windows Hosting Experience
1-888-313-9421  | webteam@orcsweb.com
  1. PowerShell’ing on Windows Server: How to import certificates using PowerShell!

    Importing a certificate to a computer…you’d think the PowerShell method would be plastered all over the web, but oh no!  I had to figure most of this out all by my lonesome.  Okay, not *all* by my lonesome, but it felt that way.  There was one set of functions I found that worked as long as you use the x86 (aka 32-bit) version of PowerShell, but since I’m a 64-bit kind of guy I prefer my code to work in both the 32-bit and the 64-bit versions of PowerShell.

     

    For your reference, the 32-bit only method is here:

     

    http://blogs.msdn.com/daiken/archive/2007/01/12/windows-powershell-met-capicom.aspx

     

    This method does not work on Windows Server 2008 64-bit because the CAPICOM object does not have a 64-bit COM+ application.  Using this code in PowerShell 64-bit gives you lots and lots of nasty red on black text.  Which meant it was time to go back to the drawing board.

     

    In case you didn’t know, PowerShell has a drive for certificates.  Just type in “set-location cert:” (minus the “”) in PowerShell and you are now in your certificate store.  I blogged about this a bit in my MD5 certificate blog post a while back, so I won’t go into that much.  This was a good start, because it let me know what .NET class PowerShell uses for its certificates by looking at the “get-member” properties of a certificate.  Why is this important, you may be wondering?  Because PowerShell is just a scripting language built on top of the .NET framework.  Which means if you know what .NET class to manipulate you can do far more in PowerShell than just reading the cmdlet help files.   In this case I used the following command to get the .NET class.

    1. gci cert:\localmachine\root | gm   
     

    ...which translates to this using the long way...

     
    1. get-childitem cert:\LocalMachine\root | get-member   
     

    I used cert:\LocalMachine\root because I know there are always certificated there, so I will get the right class I need use.  And sure enough, at the top of the get-member output you get the .NET class of “System.Security.Cryptography.X509Certificates.X509Certificate2.”  With the .NET class in hand I did a quick MSDN search and started my research.

     

    http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.aspx

     

    I’ll spare you readers the boring trial and error stuff and skip straight to the results.  There is a way to import certificates, both X.509 (.cer) and .PFX and all other supported types.  All you need to do it use one of these two handy functions.

     
    1. function Import-PfxCertificate {    
    2.   
    3.    param([String]$certPath,[String]$certRootStore = “CurrentUser”,[String]$certStore = “My”,$pfxPass = $null)    
    4.    $pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2    
    5.   
    6.    if ($pfxPass -eq $null) {$pfxPass = read-host "Enter the pfx password" -assecurestring}    
    7.   
    8.    $pfx.import($certPath,$pfxPass,"Exportable,PersistKeySet")    
    9.   
    10.    $store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)    
    11.    $store.open("MaxAllowed")    
    12.    $store.add($pfx)    
    13.    $store.close()    
    14. }   
     

    Import-PfxCertificate should be used when importing a certificate that requires a password.  I didn’t go all crazy on the password security, but you can add that outside the function in your code pretty easily.  If I add some later on I’ll do some updates to the blog post.  To use the function you pass one to four parameters.

     

    $certPath = the location of the certificate file.  Required.

    $certRootStore = either “LocalMachine” or “CurrentUser”.  Default is CurrentUser, which is your personal user store.  LocalMachine is well … the computer’s certificate store.  IIS site certificates go here.

    $certStore = any number of items.  If you go to the cert: drive, then change the directory to either LocalMachine or CurrentUser and run “dir” you will see a list of all the cert stores.  My = Personal, root = Trusted Root Certificate Authorities, etc.  These “directory names”, or certificate stores, is what you enter for this variable.  Again, didn’t go crazy with the error correction in this function, but as long as you pass the right store all is well.

    $pfxPass = the password of the pfx, or other passwords protected certificate files.  If none is specified you get a prompt, which stores the pass as a securestring.  You *can* pass a plain text password, but I wouldn’t recommend it unless you know the script is secure.  You can also pass a secure string to the function, using whatever securestring storage method you prefer…or can find.

     

    Example:

     

    Import-PfxCertificate "C:\temp\testCert.pfx" "LocalMachine" "My"

     

    The second function is for certificates that do not require a password.

     
    1. function Import-509Certificate {    
    2.   
    3.    param([String]$certPath,[String]$certRootStore,[String]$certStore)    
    4.   
    5.    $pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2    
    6.    $pfx.import($certPath)    
    7.   
    8.    $store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)   
    9.    $store.open("MaxAllowed")    
    10.    $store.add($pfx)    
    11.    $store.close()    
    12. }   
     

    The variables are the same as before, the function merely lacks password support.  Usage is identical to Import-PfxCertificate, just don’t pass it a password string or securestring.  I suppose you could, the function will just ignore it.

     

    That’s all there is to it.  If you’re still with me you may be wondering why I think this is so important.  Even working for a hosting company most certificates I handle don’t need this, even though you can use this to generate and complete certificate requests.  The reason I looked for this is because of code signing.  I can script the importation of the three required certificates for PowerShell code signing to a sever in seconds now.  Whether it was worth the effort is in the eye of the beholder, but I think it was.

    #James KehrGet-Member $OW | ?{$_.title –eq System Administrator”` –and $_.certification –match “MCITP 2008, MCSE 2000, MCDST, Network+, A+”}

    New-Variable –name company –value ‘ORCS Web, Inc.–description www.orcsweb.com | 1.888.313.9421’ 

    Thursday, August 06 2009 by | 0 comment(s)
    Tagged as: , ,

  2. Fun with PowerShell: Sorting multidimensional arrays and ArrayLists

    Sometimes the best way to learn something important is to walk the long, rocky road. And sometimes you have to use poor analogies to open a blog post because nothing better is clicking at the moment. But most of the time you wish someone else had walked the long, rocky road for you so you could pilfer the code from their blog and avoid thinking all together. My most recent rocky road, and not the ice cream kind, was sorting a multidimensional array. Or more accurately an ArrayList.

    Obviously you can use sort-object to sort an array, but what if the object you want to sort by is not the first property of the array within the ArrayList? Example: say you have four fields in an array. You store 7000 of these arrays in an ArrayList. You want to sort from the last field instead of the first. List objects such as a hashtables don’t like storing and sorting more than two fields. And since you can’t name the columns in an array or ArrayList (that I have found), sorting that multidimensional nightmare appears to be exactly that. A nightmare.

    It turns out the solution is pretty easy. This will even work for a regular multidimensional array, and not just an ArrayList. Let’s say your ArrayList is named $x. $x has 7000 arrays and four fields per array. If you want to sort by the fourth field, a.k.a. field [3] in array talk, all you need to do is this:

    $x = $x | sort-object @{Expression={$_[3]}; Ascending=$false}

    “@{Expression={$_}; Ascending=$false}”, according to the sort-object help file, gives you hashtable like control of your sort. Without the hassle of working with a large hashtable. The expression value lets you do just about anything with that pipeline variable ($_). You can even do math in that expression field. Just look at example 5 in the detailed help output (help sort -detailed). The true/false in the Ascending value just determines whether the sort does big-to-small or small-to-big.

    It turns out that sorting multidimensional arrays is as easy as eating rocky road ice cream. So stop walking, pilfer my code, and get on with your multidimensional sorting already.

    Monday, April 27 2009 by | 0 comment(s)
    Tagged as:

  3. Fun PowerShell Command: Find all folders containing a file type

    Me, write a short blog?  Never! I wanted to mass import all the drivers from the UBCD4win software to a custom WIM file, but the network drivers are scattered everywhere in different subfolders.  The solution?

    $drv = get-childitem D:\UBCD4Win\drivers -recurse -filter "*.inf" | %{$_.DirectoryName} | Get-Unique

    The first part of the command gets all the locations where an INF file lives.  Then you use a for-eachobject (%) to select the directory name of where those files are.  Get-unique automatically pulls our duplicate directories in case a directory has more than one INF file.

    In the end I do the entire import in two lines of blissfully easy PowerShell code.

    1. $drv = get-childitem D:\UBCD4Win\drivers -recurse -filter "*.inf" | %{$_.DirectoryName} | Get-Unique
    2. foreach ($d in $drv){invoke-expression "D:`\WAIK`\Tools`\PETools`\peimg`.exe `/inf=$d\*`.inf D:`\PE`\winpe_x86`\mount`\Windows"}

    Please note that I have an uncommon Windows Automated Installation Kit (WAIK) location, so please adjust accordingly and add quotes (`" inside inside the invoke-express) if there is a space in the path.

    Saturday, March 14 2009 by | 0 comment(s)
    Tagged as:

  4. Fun with PowerShell: Testing whether an IPv4 address has a valid structure.

    More fun with PowerShell today! Today's script is a function, testIP, that accepts a string input and verifies whether it is a valid IPv4 address. Someday I will write one for IPv6, but I need to figure out how exactly those 128-bits worth of rules work. *shudders*


    1. function testIP {   
    2.     param ([string]$tempIP)   
    3.   
    4.     # used for validation   
    5.     $ValidIp = $True  
    6.   
    7.     # test 1: check for proper octet length.   
    8.     [array]$crushIt = $tempIP.split('.')   
    9.     if ($crushIt.length -ne 4) {   
    10.         "The IP $tempIP is invalid. Invalid character or not enough octets."  
    11.         $ValidIp = $False  
    12.         break   
    13.     }   
    14.   
    15.   
    16.     # test 2: check for invalid characters   
    17.     if ($ValidIP) {   
    18.         # this string array stored the chars 1-9 for later testing.   
    19.         $num = "0","1","2","3","4","5","6","7","8","9"  
    20.         foreach ($oct in $crushIT) {   
    21.             $mashIt = $oct.ToCharArray()   
    22.             foreach ($m in $mashIt) {   
    23.                 if ($num -notcontains $m -and $ValidIp) {   
    24.                     "The IP $tempIP contains invalid characters."  
    25.                     $ValidIp = $False  
    26.                     break   
    27.                 }   
    28.             }   
    29.                
    30.             # test for out of range octets   
    31.             if ($ValidIP) {   
    32.                 [int32]$n = $oct  
    33.                 if ($n -lt 0 -or $n -gt 254) {   
    34.                     "$oct in $tempIP is out of range."  
    35.                     $ValidIp = $False  
    36.                     break   
    37.                 }   
    38.             }   
    39.         }   
    40.     }   
    41.     # output results...change to 'return $ValidIp' to pass result to another function.   
    42.     write-output $ValidIp  
    43. }  

    Let’s break it down, shall we? And, as usual, there may be better or perhaps built-in ways to do this. I’m not an expert programmer so doing stuff like this is a good learning experience for me, and may be helpful to someone down the line.

    First let me define what $ValidIp is and what it does. This a Boolean variable used to validate and store the results of the tests. If at any point during the function a test fails, all of the following tests will simply not run. With a couple of foreach exceptions. In the end, $ValidIp, is used to return or output the result of the tests.

    Test 1 is very simple. I use the split method of System.String to divide the string into parts, with a period (.) as the separator. The values are stored in an array named $crushIt. The if-statement then checks the length of the array (i.e. how many strings are in there). If that number is not 4, the number of octets in an IPv4 address, then the test fails and $ValidIp is changed to $False.

    Test 2 is a wee bit more complex, as it checks to make sure each character entered is a number. First it runs a check to see if test 1 failed. If it hasn’t, then the script moves along. An array is built with the character values of the numbers 0-9, followed by a foreach loop which goes through every octect. I did it this way to make test 3 go smoothly.

    $mashIt is created by converting the octet to a character array, another nifty method in System.String, and then sent through yet another foreach loop. Inside this loop each character is compared to the array storing the string values of the numbers 0-9. If any character doesn’t fit the mold the fashion show ends in tears and sorrow, $ValidIp is set to false and test 3 is summarily ignored (where the tears and sorrow come into play).

    Test 3 is the easiest rule of them all. After checking whether $ValidIp is true or false, it converts the string value of the octet to an integer (if true that is). Remember, we already made sure only numbers are involved in test 2 so there will be no conversion errors here. I looked for an hour trying to figure out the convert command, only to discover you just have to save the string to an integer variable, by using [int] or [int32] before the variable name, and PowerShell does the conversion automagically. Well, technically .NET does it, but let’s not get too picky.

    Once the conversion is done a very simple if-statement is run to see whether the value of the integer is less than zero or greater than 254. If that statement is false then $ValidIp is set to false. And now we’re done.

    I know there are a lot of other tests that could be run, especially if subnet mask was added in the picture, but that’s not the purpose of this function. This is a very simple function to make sure a letter or extra number was not fat fingered into an IP range. The script I am building this for is adds IPs to a server, this function just makes sure the script doesn’t try to add IPs 192.168.1.2 through 192.168.1.2000 to the server and cause all sorts of fun and exciting errors.

    Friday, March 06 2009 by | 0 comment(s)
    Tagged as: ,

  5. Random bytes...

    I wanted to write a script to change some drive volumes around for a new partitioning scheme.  Being me I wanted to do it in PowerShell, so I searched Google, Live, etc. to find a quick answer and ... found nothing.  Not a single person with a post on how to change a drive letter with PowerShell.  Maybe it is considered so easy no one has ever bothered blogging about it?   This needs to be corrected though, I believe, and so I shall.

    1. Grab the Win32_Volume WMI object and filter the results so you only get the drive letter you want. In my example I use E:.

      1. $volume = gwmi win32_volume | ?{$_.DriveLetter -eq "E:"}  
    2. Next up we enter an overly complex command to change the drive letter. In this case to D:.

      1. $volume.DriveLetter = "D:"  
    3. And finally we save the changes with a put().

      1. $volume.put()  

    Seriously, that's it. And now for some random notes taken from my experimentation...

    • Use $_.Label if you don't know the drive letter, but know the volume name (like OS, Data, Vista, Local Disk, etc.).
    • If you want to remove the drive letter, use this command: $volume.DriveLetter = $null (you still need the put()).
    • Mounting the volume to a folder goes like this: $volume.AddMountPoint("C:\path") Be sure the directory exists first. new-item can be used to make one, and test-path can be used to check if it's there.

    On a lighter note, I built a second system last weekend and work got me a KVM so I could switch between my work laptop and my new test system. It's a StarTech.com SV211KDVI. It can control two computers using DVI and USB for both mouse and keyboard. It also has a nice feature where you can switch between computers using a keyboard shortcut.

    I panicked today while I was using it because it kept switching between computers without me pressing the button or using the keyboard shortcut. I was about to bust out a bucket of KFC as an offering to the tech deities when I figured out my possession problem. It turns out the switching happened every time I clicked into a VMware Infrastructure Client (VIC) console window. Not the one inside the VIC, which works fine, just a breakout console window. How random is that? VMware Tools apparently uses the some keyboard code that is identical to the one StarTech uses to switch computers. Freaky stuff.

    #James Kehr
    Get-Member $OW | ?{$_.title -eq "System Administrator"`
    -and $_.certification -contains 'MCSE 2000, MCDST, Network+, A+'}

    New-Variable -name company -value 'ORCS Web, Inc.' -description www.orcsweb.com | 1.888.313.9421’

    Wednesday, February 11 2009 by | 0 comment(s)
    Tagged as: , , , , ,

  6. Fun with PowerShell: System Information, part 2

    One of the best features in PowerShell (PS) is functions.  I can’t make that any clearer.  Before diving into PowerShell I was a command prompt (CMD) geek who was scared to death of that horrible alien looking VBscript code – vbscript still does frighten me a little.  This is why I was happy to learn that PowerShell held on to its CMD roots, and that was the driving factor in getting me to start learning PowerShell.

    Being more of a scripter than a coder, though, I initially refused to work with all that object-oriented craziness and kept to my simple CMD logic.  While I was pleased to see that working with variables was much easier than with CMD, I was disappointed to see such tight execution policy restrictions.  I understand why, I just don’t like it.  Overall I was happy enough with my simple PS ways to finally, after a decade, abandoned batch files.  Then I started working on the System Information script and all that changed.

    Suddenly I had to add in unfamiliar logic operations, loops and *gasp* object-oriented code.  On top of that I had to make the output look aesthetically pleasing using HTML code which had to be uploaded to a SharePoint site.  This meant that not only was I learning new PowerShell code and techniques, but I was also using PowerShell to dynamically generate code for a completely different programming language!  Here’s the fun part, it took me a week’s worth of spare time to do it.

    Enough of the chit-chat, time to get into the script itself.  We’ll start with the main body of the program itself.


    Made with the Online Syntax Highlighter

    1. # ***** Main program body *****
    2. # Needed for the GUI
    3. [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    4. [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    5. # call the UI
    6. startGUI

    For those who have never programmed with functions and objects before, welcome to a new world of programming logic.  I do only three things with the “code body”: document (the only thing that stuck with me from my programming classes in college), call two assemblies, and then call the first function.  The rest of my code is held inside of said functions.

    One thing I learned, and I’m sure all you seasoned programmers out there will laugh when I say this, is that you should always put your functions first.  If you put the program body before the functions you get a nasty little error and nothing works.

    Now let’s take a look at startGUI to see how I really kick things off.

    Made with the Online Syntax Highlighter

    1. function startGUI {
    2. # defining $cancel, otherwise the program just exits.
    3. $cancel = $False
    4. # creates a form for input via a window
    5. $objForm = New-Object System.Windows.Forms.Form
    6. $objForm.Text = "System Information"
    7. $objForm.Size = New-Object System.Drawing.Size(310,200) # overall size
    8. $objForm.StartPosition = "CenterScreen"
    9. # Textbox for server name
    10. $objTextBox = New-Object System.Windows.Forms.TextBox
    11. $objTextBox.Location = New-Object System.Drawing.Size(10,80)
    12. $objTextBox.Size = New-Object System.Drawing.Size(260,20)
    13. $objForm.Controls.Add($objTextBox)
    14. # add keystroke options. Pressing "Enter" is the same as clicking "OK, "Esc" is the same as "Cancel"
    15. $objForm.KeyPreview = $True
    16. $objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
    17. {$Server=$objTextBox.Text;$objForm.Close()}})
    18. $objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
    19. {$cancel = $True;$objForm.Close()}})
    20. # Draw 'OK' button and sets button action.
    21. $OKButton = New-Object System.Windows.Forms.Button
    22. $OKButton.Location = New-Object System.Drawing.Size(75,120)
    23. $OKButton.Size = New-Object System.Drawing.Size(75,23)
    24. $OKButton.Text = "OK"
    25. $OKButton.Add_Click({$Server=$objTextBox.Text;$objForm.Close()})
    26. $objForm.Controls.Add($OKButton)
    27. # Draw 'Cancel' button and sets button action.
    28. $CancelButton = New-Object System.Windows.Forms.Button
    29. $CancelButton.Location = New-Object System.Drawing.Size(150,120)
    30. $CancelButton.Size = New-Object System.Drawing.Size(75,23)
    31. $CancelButton.Text = "Cancel"
    32. $CancelButton.Add_Click({$cancel = $True;$objForm.Close()})
    33. $objForm.Controls.Add($CancelButton)
    34. # Adding labels for text boxes.
    35. # Label for server name prompt
    36. $objUser = New-Object System.Windows.Forms.Label
    37. $objUser.Location = New-Object System.Drawing.Size(20,20)
    38. $objUser.Size = New-Object System.Drawing.Size(280,40)
    39. $objUser.Text = "Enter a hostname or IP address."
    40. $objForm.Controls.Add($objUser)
    41. # Puts the form on top of all other windows.
    42. $objForm.Topmost = $True
    43. # Activates/draws the form.
    44. $objForm.Add_Shown({$objForm.Activate()})
    45. [void] $objForm.ShowDialog()
    46. mainFunc -Server $server -Cancel $cancel
    47. }

    When PowerShell 2.0 comes out we PowerShellers will be able to use the Windows Presentation Foundation (WPF) to make some pretty slick interfaces, until then we are stuck with Windows Forms.  Not as pretty, but still functional.  Incidentally, those two assemblies called in the main program body are for the GUI.  I call them in the program body instead of the function itself so I only have to call them once and not each time I run the function.

    You can actually run this code directly from the PowerShell command line window by copy/pasting the two assembly calls then the guts of the function, minus the last line – which calls the next function in the program.

    This is a very simple UI that asks for a hostname or IP.  You have two selection options, OK and Cancel, which can be triggered by either clicking the buttons or using Enter/Escape on the keyboard.  The form body consists of a text box, where the user can enter information, and a label for the text box.  I would love to say that I came up with this all my lonesome, but since this is slightly modified code ripped straight from one of Microsoft’s “Windows PowerShell Tip of the Week” posts I won’t.  Instead I’m going impart you with a two links that I have found invaluable when creating Windows Forms from PowerShell, and then let the experts tell you how it all works.

     

    PowerShell Tip of the Week link:

    http://www.microsoft.com/technet/scriptcenter/resources/pstips/feb08/pstip0208.mspx

    Details on the System.Windows.Forms namespace:

    http://msdn.microsoft.com/en-us/library/system.windows.forms.aspx

     

    The second link is especially handy when you decide to build more complex interfaces.  For example, one of the latest interfaces I built is nearly 200-lines and uses labels, textboxes, checkboxes and a dynamically populated listbox.  All of which I figured out by understanding the code from the PowerShell tip above and by reading about the forms namespace.

    Before I close this blog post out I will impart a few bits of UI wisdom I have learned.  First, document well.  Well documented UI elements, and code for that matter, make it easier for you to recall and reuse code.  Second, keep your code neat.  You don’t have to follow my format, but the cleaner you keep your code the easier it is to recall and reuse it.  Notice the pattern yet?  Third, there is no miracle way to easily make a UI.  Create a second script separate from the one you are building specifically for generating the UI so you can tweak it easily and quickly.  It will take time and patience to get everything lined up properly.  And lastly, Windows forms work differently on different systems.  I hate saying this, but a form that looks perfect on my Vista laptop may look horrible on a 2003 server.  Either build some test VMs or find some systems to test your UI on so you can find the best common settings for all.

    Hopefully the WPF support in PowerShell 2.0 will fix a lot of the UI woes in PowerShell 1.0.  I haven’t had time to dig into WPF support, but if you are interested there is a great series of articles about it starting here:

    http://blogs.msdn.com/powershell/archive/2008/05/22/wpf-powershell-part-1-hello-world-welcome-to-the-week-of-wpf.aspx

    In part 3 I will dive into the main function body, followed by generating the HTML in part 4.  Part 3 coming soon™.

    #James Kehr
    Get-Member $OW | ?{$_.title -eq "System Administrator"`
    -and $_.certification -contains 'MCSE 2000, MCDST, Network+, A+'}

    New-Variable -name company -value 'ORCS Web, Inc.' -description www.orcsweb.com | 1.888.313.9421’

    Wednesday, December 24 2008 by | 0 comment(s)
    Tagged as: , ,

  7. Fun with PowerShell: Installing the OpsMgr 2007 Agent

    I ran into an interesting dilemma yesterday.  While converting my two OpsMgr 2007 Agent install scripts from batch file to PowerShell I hit a snag with the most important part of the script, the actual installation of the OpsMgr Agent.

    To manually install the OpsMgr Agent with customizations you have to use msiexec.exe with a ton of flags.  Doing a cut & paste of the install line from the batch file was a resounding failure.  So was using an invoke-expression and several other methods I tried.  Eventually I ran into this forum post which gave me the solution I needed.

    http://www.vistax64.com/powershell/101954-executing-cmd-exe-powershell.html

    I'm not going to bother with the entire script, but my installOMAgent function ended up looking like this:



    Made with the Online Syntax Highlighter

    1. function installOMAgent {
    2.  # WMI call to get OS info
    3.  $os = get-wmiobject -class "Win32_OperatingSystem" -namespace "root\CIMV2"
    4.  # check OS architecture version, store result in $arch
    5.  if ($os.OSArchitecture -eq "64-bit" -or $os.Caption -match "x64") {
    6.   $arch = 64
    7.  } else {
    8.   $arch = 32
    9.  }
    10.  # set install dir
    11.  $INSTALLDIR = 'D:\Program Files\System Center Operations Manager 2007'
    12.  # set installer arguments
    13.  $argue = 'USE_SETTINGS_FROM_AD=0 MANAGEMENT_GROUP=MgtGrp MANAGEMENT_SERVER_DNS=server.domain.top SECURE_PORT=999999 ACTIONS_USE_COMPUTER_ACCOUNT=1'
    14.  switch ($arch) {
    15.   32 {
    16.    # generates the install string for the x86 client
    17.    $call = "msiexec`.exe `/i V:`\x86`\MOMAgent`.msi `/qn `/promptrestart INSTALLDIR=`"$INSTALLDIR`" $argue"
    18.   }
    19.   64 {
    20.    # generates the install string for the x64 client
    21.    $call = "msiexec`.exe `/i V:`\AMD64`\MOMAgent`.msi `/qn `/promptrestart INSTALLDIR=`"$INSTALLDIR`" $argue"
    22.   }
    23.    default {"Unsupported operating system architecture.";exit}
    24.  }
    25.  # installs the agent
    26.  $p = [diagnostics.process]::Start("cmd.exe","/c start /wait $call")
    27.  $p.WaitForExit()
    28.  # if 2008 then add the firewall rule
    29.  if ($os.version.chars(0) -eq "6") {
    30.   "Adding 2008 firewall rule..."
    31.   netsh advfirewall firewall add rule name="OpsMgr Agent" action=allow protocol=TCP dir=in localport=999999
    32.  }
    33. }


    The code is fairly easy to follow.  First, I pull the OS information using a WMI call, then determine if the OS in question is 32- or 64-bit.  The $arch variable is then created to determine the MSI path for the appropriate agent version.  I then break down the agent install line into variables.  $INSTALLDIR sets the install location, and $argue sets the rest of the custom installation arguments I chose for the installation.

    From there I run a switch statement to finish the installation command-line based on what operating system architecture is present and then store that finished string in variable named $call.  The installation then runs, pausing the script during so it does not continue until the application is present.  Lastly I run one last check to see if the OS is Windows Server 2008. If it is I add the firewall rule needed for the health service to work in the prescribed bi-directional manner.

    That's it!  Piece of cake.  As with all PowerShell functions, this little snippet of code can easily be modified for any number of .msi packages.  Just tweak the contents of a few variables, dump the code into a different installation script and viola!  New installer script in record time.

    #James Kehr
    Get-Member $OW | ?{$_.title -eq "System Administrator"`
    -and $_.certification -contains 'MCSE 2000, MCDST, Network+, A+'}

    New-Variable -name company -value 'ORCS Web, Inc.' -description www.orcsweb.com | 1.888.313.9421’

    Tuesday, December 23 2008 by | 0 comment(s)
    Tagged as: , , ,

  8. Fun with PowerShell: System Information, part 1

    When it comes to system administration I have one very simple rule: all repetitive tasks should be scripted.  Sure you may spend a day or two, or longer for those especially pesky tasks, hammering out a working script, but once that script is written you can use it indefinitely for years to come.  And what better way is there to script in Windows than PowerShell?  I dare you to say VBscript, I could use a good laugh today – I’ve had a nasty head cold for the past few days.

    Speaking of being sick, why is it that you always get sick on, or just before, a weekend or holiday? 

    Back to the task at hand.  To make a long story short, and those who know me can attest to my long-windedness, I needed to develop a script which would call our SQL server and grab a list of available servers, then remotely pull the server’s detailed hardware and operating system information through WMI, turn the WMI goobly-gook into pretty HTML, and lastly upload that information to the sales team’s SharePoint site.  Piece of cake, right? 

    Did I mention the most I’ve programmed since I completed my Java programming class in 1998 were some simple batch files?  I didn’t think so.  This script is a testament to how easy it is to learn PowerShell, how strong the community is, and how flexible the language/shell is.  PowerShell 2.0 is going to be even better, too.  But I digress, let me show you the outcome of my system information script – slightly edited for security reasons.

    Computer Name

    XXXXX

    Make and model

    Dell Inc. PowerEdge M600

    Dell Serial #: 1XXXXX1

    Operating System

    Microsoft® Windows Server® 2008 Standard 64-bit

    RAM

    Physical memory: 8 GiB

    • DIMM1 = 1 GiB
      • Model: HYMP512F72CP8D3-Y5
    • DIMM2 = 1 GiB
      • Model: HYMP512F72CP8D3-Y5
    • DIMM3 = 1 GiB
      • Model: HYMP512F72CP8D3-Y5
    • DIMM4 = 1 GiB
      • Model: HYMP512F72CP8D3-Y5
    • DIMM5 = 1 GiB
      • Model: HYMP512F72CP8D3-Y5
    • DIMM6 = 1 GiB
      • Model: HYMP512F72CP8N3-Y5
    • DIMM7 = 1 GiB
      • Model: HYMP512F72CP8D3-Y5
    • DIMM8 = 1 GiB
      • Model: NT1GT72U8PB1BN-3C

    Total available memory slots: 8

    Memory slots in use: 8

    Unused memory slots: 0

    CPU

    No. of CPUs: 1

    Intel(R) Xeon(R) CPU E5405 @ 2.00GHz

    • No. of cores: 4
    • Details: Intel64 Family 6 Model 23 Stepping 6

    Total No. of logical CPU cores: 4

    Disk information

    Number of Virtual Disks: 2

    Virtual disk 1 size: 72 GB

    • Partition 1
      • Drive C:
      • Name: OS
      • Size: 21.5 GB
      • Free: 1.4 GB
    • Partition 2
      • Drive D:
      • Name: Data
      • Size: 50.5 GB
      • Free: 16.9 GB

    Physical disk(s):

    ST973402SS
    Google details

    ST973402SS
    Google details

    Network Information

    NICs

    • Broadcom BCM5708S NetXtreme II GigE
    • Broadcom BCM5708S NetXtreme II GigE

    IP Addresses:

    •  
      • xxx.xxx.xxx.xxx
      • xxx.xxx.xxx.xxx
      • ::1
    • v4

      v6

    I also made a stand-alone variant of this script for data center duties so I can get a quick peek at a system if a client requests an upgrade. This will be the variation on the system information script that I will ultimately be detailing in this series of blog posts.  After that I’ll go into customizations that can easily be implemented by strategically adding a function here and there.  Tune in next week and I’ll get into the juicy yumminess of the script.

     

    #James Kehr

    Get-Member $OW | ?{$_.title –eq System Administrator”`

     –and $_.certification –contains “MCSE 2000, MCDST, Network+, A+”}

    New-Variable –name company –value ‘ORCS Web, Inc.–description www.orcsweb.com

    Tuesday, November 25 2008 by | 0 comment(s)
    Tagged as: , ,

  9. PowerShell Pearl: Filter by Contained Text

    I have just recently started using PowerShell. While this blog will not be where you want to go to learn PowerShell, as I pick up little pearls here and there, I will try to share them with small samples and quick PowerShell scripts. I am no PowerShell expert, so if you find any errors, please let me know. 

    Today's Pearl: 

    If you are returning a set of results and you want to filter those results by text contained in one of the fields there are two ways I found you can do this. The first is using a Field.Contains(“search text”) –eq “true” and the other (thanks Scott) is doing a Field –match “search text”. 

    So if you wanted to see all of the System Event logs that have cmd.exe is the message you could get this using either of these methods: 

    Get-EventLog system | where { $_.Message.Contains("cmd.exe") -eq "true" }

    or…

    Get-EventLog system | where { $_.Message -match "cmd.exe" }

    Either of these can be used in the negative form just as easily:

    Get-EventLog system | where { $_.Message.Contains("cmd.exe") -eq "false" }

    Get-EventLog system | where { $_.Message -notmatch "cmd.exe" }

    That’s it! The –match and –notmatch are probably the easier of the two to use, although I am sure there is a reason for each of them that I am not aware of. Hopefully things like this will come to be part of my knowledge as I know more.

    Update:

    Okay, I just learned that -match and -nomatch are regular expression comparison operators. Another set comparison operators you could use are -like and -notlike. These are the wildcard comparison operators. It could be used like so:

    Get-EventLog system | where { $_.Message -like "*cmd.exe*" }

    Wednesday, February 21 2007 by | 0 comment(s)
    Tagged as: ,