Use PowerShell Instead of cURL

I recently had to come up with a way to schedule regular downloads of a file from a website that uses cURL. cURL is a command line tool for transferring data with URL syntax.  Getting cURL approved for use in my company is a bit difficult, so I set out to find a PowerShell solution. What I found was surprisingly easy to write and implement. You may want to take a look at the cURL Sourceforge page to get more information. Keep in mind that cURL parameters are specific to the website you are downloading from so every website will be different. You may need to contact that site’s tech support to find out what you need to pass if you can’t figure it out from the URL.

As always, if you use this script, please give credit.

$path     = "c:\PoShOut\"
$username = "username"
$password = "password"
$filename = "somefile.zip"
$somefile = "downloadedfile.zip"
 
# Create COM object, open a connection to www.somewhere.com,
# and set the header type
$document = New-Object -ComObject msxml2.ServerXMLHTTP
$document.open("POST", "https://www.somewhere.com", $false)
$document.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
 
# Create the necessary login and report information from the parms we passed
$postline = "user=" + $username + "&password=" + $password + "&filename=" + $filename
 
# Login to Markit with the information we passed above
$document.send($postline)
 
# Check to see if we have a document matching the parms we passed
$response = $document.getResponseHeader("Content-Type")
 
# If we have a document, make sure it's a Zip file, get the filename,
# and put the Zip into a Byte array. Once the file is downloaded, write
# the Byte array to a file.
If($response.Substring(0,17).ToLower() -eq "application/x-zip")
{
     $filename = $response.Substring(24)
     [Byte[]]$file = $document.responseBody
     [System.IO.File]::WriteAllBytes($path + $filename,$somefile)
}

Fix Windows Live Movie Maker’s Black Screen When Editing Fraps Videos

I’ve recently decided to make some YouTube videos of how to find Datacrons in SW:TOR that I recorded with Fraps. My video editor of choice is typically Windows Live Movie Maker because:

    a.) It’s easy to use
    b.) It’s cheap (read: FREE)

However, for some reason, the preview screen only showed black. I dug out Google and Bing and began looking for solutions. I saw all kinds of solutions from uninstalling/reinstalling everything to using another app to import, convert, and export the Fraps movie to a format that WLMM could render. I know when I made my iRacing vids, I didn’t have this problem. So, I kept digging.

Finally, I happened upon a post on Windows 7 Forums that struck a chord. I have two Nvidia graphics card and after an upgrade to the 285.62 drivers, DCS: A-10 Warthog had some wierd graphical anomalies. There is a bug in this revision of the driver that causes chunks of the world to either render black or transparent.

So, with that in mind, I hit Nvidia’s website and downloaded the previous WHQL drivers (280.26) and voila! No more black preview screen. So, if you are having this problem I recommend getting the 280.62 drivers, or you can play around and find the latest drivers that fix this issue.

Use Your Logitech G25/G27 Pedals as Rudder Controls

If you are into flight sims like Flight Simulator X, X-Plane, or IL-2, you know that it’s much more realistic and much cooler to pilot your airplane with real rudder controls.  Unfortunately, the CH rudder pedals are kind of a one trick pony and kind of expensive.  If you happen to also be a sim racer and you happen to have a G25 or G27 wheel with pedals, you’re in luck.  You can used those G2x pedals as your rudder pedals for most flight sims.  I’ve been able to use them for FSX, IL-2, X-Plane, and Wings of Prey.  Theoretically, this should work with anything that uses rudder-type controls. It’s relatively to use the accelerator and the brake as rudder pedals.  The Logitech Profiler will do that for you by combining the accelerator and brake axis.  However, the brake has a much stiffer throw than the accelerator and, for me, it kind of breaks the illusion of flying.  So, we’ll learn how to combine your clutches axis with the accelerator so your feet will be in a more natural position and the throw will feel roughly the same on both pedals.

GET YOUR STUFF

What you’ll need at a minimum is:

  • Logitech G25/G27 Pedals
  • GlovePIE
  • PPJoy (version 0.8.4.5 early release)
  • Microsoft Windows

I also have a Bodnar box which allows me to connect my pedals directly to my computer.  It gives the pedals a much higher resolution than having them connected through the wheel.  The instructions here assume you have one.  If you don’t, you’ll still be able to make this work, but you’ll have to fiddle with the GlovePIE program a bit.  More on that later.

EXTRACT GLOVEPIE

Once you’ve downloaded all of the necessary software, you can begin by extracting GlovePIE.  It’s not an install, so just uzip it to wherever you are going to run it from.  After you’ve extracted the software, you’ll probably want to put a shortcut to GlovePIE somewhere you can launch it before launching your favorite sim.  You’ll be using GlovePIE.exe everytime you want to turn your G25 pedals into rudder pedals.

INSTALL PPJOY

After you’ve taken care of GlovePIE, you’ll need to install PPJoy.  Here is where the fun begins if you are a Windows 7 x64 user (if you are a 32 bit Windows user, you can skip straight to the configuration part).  Microsoft enforces driver signing in 64 bit Windows 7 for security purposes.  This is a good deal for security, but not so much for small hobbyist developers like Deon Van Der Westhuysen.  A Verisign certificate costs upwards of $500 per year…not very attractive for a guy just trying to help the community. In any case, there is good news for us in the x64 boat.  When you launch the installer, you’ll see a message box with some cryptic commands in it.  Essentially, this is what’s going to allow us to install and run PPJoy.  If you’ve begun installing PPJoy, click cancel.  Open a command prompt by clicking Start, then Run, type CMD, and hit Enter (or you can hold the Windows key and press R).  In the DOS window, type the following and hit enter:

BCDEDIT -SET TESTSIGNING ON

After you’ve turned Test Signing on, you you see a message on your desktop telling you that you are in Test Mode.Windows 7 Test Mode If you don’t see this message, PPJoy will not work.  It’s worth noting that if you have the User Account Control turned on, you may need to run the command prompt as the local administrator.  You can do this by finding the shortcut for the command prompt in the Start Menu, right-clicking it, and selecting Run As Administrator.

If you see this message, congratulations, you’ve just made your computer slightly less secure!  I recommend extreme caution when installing anything while running in Test Mode.  When not using your pedals as rudder pedals, I recommend turning this off by running the same command, but change ON to OFF.  Unfortunately, this seems to break the PPJoy install and you’ll need to reinstall it.  So, I’ve warned you about this.  If you goon your computer, I’m not legally, financially, morally, or emotionally responsible.  Like my old First Sergeant used to say, “Fair warning is fair play.”  You can go ahead an continue installing PPJoy like you would any other piece of software.

CONFIGURE PPJOY

After you’ve installed everything, you’ll need to configure a virtual joystick port.  Click Start, Programs, All Programs, and find the PPJoy Joystick Driver folder and launch “Configure Joysticks” and “Add” a joystick.  After adding a joystick, we need to configure it.  You can just use the defaults if you want, but it adds a bunch of stuff I don’t like.  Start the configuration process by clicking “Mapping” and doing the following:

  • Leave the default setting of “modify the mapping of this controller” and click Next
  • Change the Axes dropdown to 2 and make sure there are 0 buttons or POV hats selected (you may only need one axis here, but I chose 2 just to be safe)
  • Change Axis 1 to X Rotation and Axis 2 to Z Rotation and click Next
  • Make the Min setting on Axis 1 to Analog 0 and Axis 2 to Analogue 1 and click Next
  • Click Next on the next screen (everything should be grayed out anyway)
  • Click Finish

Windows Game ControllersYou can click “Done” on the PPJoy configuration window.  If everything is right with the world, you should see a PPJoy Virtual Joystick 1 entry in your Game Controllers settings.  If your Game Controllers window doesn’t show a virtual joystick, you’ll need to repeat the steps above.  It’s very important that a virtual controller shows up here.

CONFIGURE GLOVEPIE

Configuring GlovePIE is sometimes both the easiest and most frustrating part of this process.  GlovePIE is what turns your virtual joystick into a single axis that your flight simulator will be able to use as the rudder axis.  The biggest hurdle you have to overcome is to find out what your axes your computer reads on your virtual joystick.  Probably the easiest way is to buy FSUIPC. It’s an addon for FSX that really does a ton of stuff, but I use it for joystick calibration.  It will also show you what axis your rudder pedals use.

In any case, when launched, GlovePIE is pretty nondescript.  GlovePIEIt just a window with a three tabs and a run button.  We’re interested in the tab that’s labeled “Untitled”.  That’s where we write our program that will tell GlovePIE to turn our accelerator and clutch into one axis that our flight sim can use.  Depending on your pedal configuration, this is going to be much different that what I’ve written for my pedals.  If you have a Bodnar box, you will probably be able to just take the GlovePIE program I’ve written and use it for your pedals.  If you don’t…well, that’s where the frustration happens.  You can try every random axis your pedals could possibly use and you may stumble on the correct ones, but you may not either.  This is where FSUIPC will be helpful.  It’s been a while, but I think you need the full version in order to find joystick assignments.  If you don’t have FSUIPC and can’t afford it, you may want to check the internet and see if you can find the axes that the accelerator and clutch use.

If you have a Bodnar box, the program I’ve written should mostly just work.  There is some modification you may have to make depending on the number of joysticks you have and where your virtual joystick shows up in Game Controllers.  We’ll talk about that in a minute, but for now, just copy and paste the code below in to GlovePIE and save your program someplace you can find it (I keep mine in my GlovePIE directory).

///Combined throttle (X Rotation) and clutch (Z Rotation)
///make virtual joystick axis.  This is required if your
///G25 pedals are using a Bodnar Box.

PPJoy1.Analog0 =  (.5*Joystick2.U) - (-.5*Joystick2.R)

 

The breakdown of the code here is PPJoy1.Analog0 means that your are working with the first PPJoy joystick on the Analog 0 axis.  This is the Analogs you set in the PPJoy configuration.  The (.5*Joystick2.U) – (-.5*Joystick2.R) is where we configure how the axis works.  Before I break this down, I need to point out that this is where your configuration could differ quite a bit depending on whether you have a Bodar box, how many joysticks you have, and where your virtual joystick appears in your Game Controllers control panel.  I have 3 joysticks listed and my PPJoy joystick appears third in the list, so I use Joystick2.  If you are a programmer or a scripter, you probably understand why my joystick appears third in the list and is Joystick2.  Your joystick count starts at zero.  So, your first stick is Joystick0, your second is Joystick1, and your third is Joystick2.  Hey, I didn’t make the rules.

In real aircraft rudder pedals, each pedal is only responsible for half the total movement of the rudder.  The code above takes half of clutch (.5*Joystick2.U) and half of the accelerator (-.5*Joystick2.R) and combines them to be the a single axis (X Rotation).  Notice the accelerator uses a negative number in the assignment. It’s complicated, but this is necessary to make the accelerator work correctly.  If anyone really wants to understand why that is, let me know and I’ll explain it.

ASSIGN YOUR AXIS

Now that you have your axis created and functioning correctly, you need to go to the control panel of your favorite flight sim and configure them to be your rudder pedals (you need to make sure you have GlovePIE up and running the program we just created).  If you’ve done everything correctly, when you press either the accelerator or clutch, your flight sim should assign your new pedals axis to the rudder.

 

Hopefully this is helpful to somebody who has been struggling getting this functioning in x64 Windows or somebody just looking to add rudder pedals to their flight sim without investing more money in yet one more peripheral.  Please let me know if this has been helpful.  if you run across any mistakes, let me know if the comments.

Really Kill that Excel Object

If you’ve tried to use Excel 2007 or above with any of your Powershell scripts, you know that more often than not, when you quit Excel, a process stays running.  Not a huge deal until you’ve got a script scheduled to run several times a week and you end up silly with memory-eating Excel processes. You could kill all EXCEL.EXE processes, but what if you’ve got a scheduler that may be running other Excel processes as well?  If you kill all  running EXCEL.EXE processes, you’ll also be killing other Excel processes that should be running.

I’ve managed to put together some code that will determine what Excel processes are running before your create your Excel object and kill only the process spawned by your script.

## Grab all EXCEL.EXE processes before we create our Excel object.  This will find
## all Excel processes that we don't want to kill.  After we have those, we create
## our Excel object and immediately look at the Excel processes afterward.  There
## obviously a slight chance that you'll grab a process that spawned between our
## $startprocs and $endprocs scans, but it's a pretty small chance.
$startprocs = Get-Process | Where {($_.Name.ToLower() -eq "excel")}
$excel      = New-Object -ComObject Excel.Application
$endprocs   = Get-Process | Where {($_.Name.ToLower() -eq "excel")}
 
//             Your Code Goes Here                                        //
 
## Try to quit Excel (after you've saved your doc).  It's probably not going to work,
## but we're gonna try anyway.
$excel.Quit()
 
## Check to make sure $startprocs isn't null.  If it is null and we don't check, the
## script will blow.  If $startprocs is null, we're going to kill the only running
## Excel process...the one we created.
If($startprocs -gt $null)
{
     $compare = Compare-Object $startprocs $endprocs
     $process  = $compare | % {If($_.SideIndicator -match "=>") {$_.InputObject}}
}
else
{
     $process = $endprocs
}
 
Kill -Id $process.Id

Use Powershell to Pass Embedded Credentials

I needed to pass the ID/password of a service account to a Microsoft HPC grid for some jobs we were submitting automatically.  Powershell was able to do that pretty easily.

***WARNING***  This script reads a domain ID and password from an unencrypted file.  Our implementation of this script used a protected file.  Yours should too!  You’ve been warned.  I’m not responsible if your account password is compromised.

$domain      = "[Your domain name]"
$filename    = "[Path to filename]\filename"
$username    = $domain + "\" + ($username.substring(($username.Length - 5),5))
$password    = Get-Content $filename | ConvertTo-SecureString -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential($username,$password)

A couple notes here.  The $filename variable is used to get the entire path of the password file.  For ease of scripting, I’ve created a file named the same as the user ID I’m using with no file extension.  In the $username line, I’ve concatenated the $domain variable, a backslash, and the user ID that I’ve derived by taking the substring of the length of the $filename variable, minus the length of the ID, plus the length.  There is a cleaner way to do this, but the ID I’m using will never change (yes, I said never), so I chose this method.

$username    = $domain + "\" + ($username.substring(($username.Length - 5),5))

From that point, we read the password from the file and pipe it into the ConvertTo-SecureString commandlet.  Since we are using a plaintext password here (see the warning above), we have to use the -AsPlainText and  -Force parameters.  If you don’t use these, PoSh will complain, you’ll be frustrated.

$password    = Get-Content $filename | ConvertTo-SecureString -AsPlainText -Force

The last line is used to actually pass the credentials to a variable that can be used in a script that requires authentication.

$credentials = New-Object System.Management.Automation.PSCredential($username,$password)

Turn Off the UAC with Powershell

I wrote this script to turn off the UAC on our HPC R2 grid with 64 servers.  It works very well, but there is not any real error handling in there to account for offline computers.  If you are doing this for Windows 7 computers that may or may not be online, you may want to add some custom ping checks to keep the script from blowing chunks.

As always, uses the script in whole or in part, but I’d appreciate some props if you do.

########################################################################
#
#	Disable User Account Control
#
#	Author:  Jim Melton
#
#	This script checks the registry for all computer in a flat file
#		and checks to see if the UAC is turned on.  If it is, it will
#		set the EnableLUA value to 0.  The script can be modified to
#		enable the UAC if need be.  A machine reboot is required to
#		complete the change.
#
########################################################################
 
########################################################################
#
#	Initialize variables and set paths
#
########################################################################
$computers = Get-Content ""
$WriteUAC = $true
$UACvalue = "EnableLUA"
$UACoff   = 0
$UACpath  = "Software\Microsoft\Windows\CurrentVersion\policies\system"
 
########################################################################
#
#	Loop through the input file for all computers, open the remote
#		HKLM hive using .NET, open the subkey to the UAC setting, and
#		get the Enable UAC value.
#
########################################################################
ForEach($computer in $computers){
 
  $OpenRegistry = [Microsoft.Win32.RegistryKey]::`
	OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$computer)
 
  $UACsubkey = $OpenRegistry.OpenSubKey($UACpath,$WriteUAC)
  $UACsubkey.ToString()
  $UACstate  = $UACsubkey.GetValue($UACvalue)
  Write-Host "The UAC on $computer is currently set to $UACstate."
 
########################################################################
#
#	If the EnableLUA value is set to 1, set it to 0.  Grab the EnableLUA
#		value after the change to make sure it was successful.
#
########################################################################
 
  If($UACstate -eq 1)
    {
	Write-Host "Turning the UAC off..."
	$UACsubkey.SetValue($UACvalue, $UACoff)
	$UACstate = $UACsubkey.GetValue($UACvalue)
	Write-Host "The UAC on $computer is now set to $UACstate.`
             The computer needs to be rebooted."
    }
}

Determine PC SAS Bundles

Here is a PoSh script that I wrote to determine what bundle of PC SAS is installed.  If you aren’t familiar, there are a lot of components for PC SAS and it’s expensive.  They are installed in “Bundles” and you are billed based on what bundle is installed.  The more components in the bundle, the higher the license cost. 

Unfortunately, when SAS is installed, there is no method for determining which bundle is installed because the SAS installer makes no note of what bundle or what components are installed.  I had to come up with a method for determining what bundle each user had on his/her computer.  The script below is a result of installing every bundle we had and analyzing what components where in each bundle…and quite a bit of swearing.

As always, feel free to use this code, but please give credit.

#############################################################################
##																			#
##	PC SAS Bundle Finder													#
##																			#
##		Written by Jim Melton												#
##
##																			#
##	Reads computer names with PC SAS installed from a spreadsheet created	
##	by SMS or some other means and determines what version of PS SAS is		#
##  installed.  Also determines what bundle is installed, as well as 		
##	whether or not the Graph and Enterprise Guide components are installed	
##																			#
##	Known Issues:															#
##		-If PC SAS installed incorrectly, the bundle cannot be determined.	#
##		-If WMI is broken on the computer, the logged on user will not be	#
##			determined.														#
##		-When Excel quits at the end, the Excel process has a tendency		#
##			to remain running.  You may need to kill these at some point.	#
##																			#
#############################################################################
 
$ErrorActionPreference = "Continue"
 
##
##  Put the path to your text,csv, or xls here
##
$computers = Get-Content "<path to csv>\pcsasinstalled.csv"
 
##
##	Create the objects to ping machines and open Excel
##
$ping = New-Object System.Net.NetworkInformation.Ping
$excel = New-Object -ComObject excel.application
 
##
## You can change this to $false if you don't want to see Excel
##
$excel.visible = $true
$worksheets = $excel.workbooks.add()
##
##	Create the spreadsheet headers
##
$sheet = $excel.worksheets.item(1)
$sheet.cells.item(1,1) = "Computer Name"
$sheet.cells.item(1,2) = "Username"
$sheet.cells.item(1,3) = "SAS Version"
$sheet.cells.item(1,4) = "Bundle"
$sheet.cells.item(1,5) = "Graph Installed?"
$sheet.cells.item(1,6) = "Enterprise Guide Installed?"
$introw = 2
$workbook = $sheet.usedrange
$workbook.font.bold = $true
##
##	Bundle finder function
##
Function Bundles {
  ForEach ($computer in $computers){
  ##
  ##	Zero variables
  ##
  $eguide = $false
  [int]$count = 0
  ##
  ##	Set variable for pinging computers
  ##
  $reply = $ping.send($computer)
  ##
  ##	Ping computer and see if it is on the network.  If it is, open the registry in $pathtest
  ##	to see if SAS is installed and if so, what version.  Also open the registry in $basepathtest
  ##	to see if the Enterprise Guide is installed.
  ##
	If ($reply.status -eq "success"){
	$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$computer)
	$basepathtest = $registry.OpenSubKey("SOFTWARE\SAS Institute Inc.")
	  $pathtest = $registry.OpenSubKey("SOFTWARE\SAS Institute Inc.\The SAS System")
	If ($pathtest.GetSubKeyNames() -contains "9.1"){
	  $sasver = "9"
	  $components = $registry.OpenSubKey("SOFTWARE\SAS Institute Inc.\The SAS System\9.1\Setup\Components")
	}
	ElseIf
	  ($pathtest.GetSubKeyNames() -contains "8"){
	  $sasver = "8"
	  $components = $registry.OpenSubKey("SOFTWARE\SAS Institute Inc.\The SAS System\8\Setup\Components")
	}
	  ##
	  ##	Check to see if the Enterprise Guide is installed and if so, set $eguide to $true
	  ##
	  If ($basepathtest.GetSubKeyNames() -contains "Enterprise Guide"){
		$eguide = $true
		}
	  ##
	  ##	Get SAS component names and assign to $bundle.  Also, do WMI query on $computer
	  ##
	  $bundle = $components.GetValueNames()
	  $user = gwmi Win32_computersystem -ComputerName "$computer"
	##
	##	Start the analysis of each bundle installed.  Count the number of components installed by
	##	incrementing [int]$count.
	ForEach ($component in $bundle){
	  [int]$count = [int]$count + 1
	}
	  ##
	  ##	Do this if SAS 8 is installed.  When the number of components installed is determined,
	  ##	write the computer name, logged on user (from WMI query above), SAS version, whether Graph is
	  ##	installed, and whether the Enterprise Guide is installed.
	  ##
	  If ($sasver -eq "8"){
	 	Switch ($count){
	  	  8 {If ($bundle -contains "fsp"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 9"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
			      $sheet.cells.item($introw,6) = "Yes"
				}
			  }
			}
		  7 {If ($bundle -contains "connect"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 8"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			}
			  ElseIf ($bundle -contains "eis"){
				$sheet.cells.item($introw,1) = $computer
				$sheet.cells.item($introw,2) = $user.username
				$sheet.cells.item($introw,3) = $sasver
				$sheet.cells.item($introw,4) = "Bundle 7"
				  If ($bundle -contains "graph"){
					$sheet.cells.item($introw,5) = "Yes"
					}
					If ($eguide -eq $true){
					  $sheet.cells.item($introw,6) = "Yes"
					}
				}
			}
		  6 {If ($bundle -contains "assist"){
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "Bundle 6"
				If ($bundle -contains "graph"){
				  $sheet.cells.item($introw,5) = "Yes"
				  }
				  If ($eguide -eq $true){
					$sheet.cells.item($introw,6) = "Yes"
					}
				}
			}
		  5 {If ($bundle -contains "stat"){
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "Bundle 5"
				If ($bundle -contains "graph"){
				  $sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			}
			Else {
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "Bundle 4"
				If ($bundle -contains "graph"){
				  $sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				 }
				}
			}
			4 {If ($bundle -contains "pcfile"){
				$sheet.cells.item($introw,1) = $computer
				$sheet.cells.item($introw,2) = $user.username
				$sheet.cells.item($introw,3) = $sasver
				$sheet.cells.item($introw,4) = "Bundle 3"
				  If ($bundle -contains "graph"){
					$sheet.cells.item($introw,5) = "Yes"
					}
				  If ($eguide -eq $true){
					$sheet.cells.item($introw,6) = "Yes"
					}
				}
				Else{
				  $sheet.cells.item($introw,1) = $computer
				  $sheet.cells.item($introw,2) = $user.username
				  $sheet.cells.item($introw,3) = $sasver
				  $sheet.cells.item($introw,4) = "Bundle 2"
					If ($bundle -contains "graph"){
					  $sheet.cells.item($introw,5) = "Yes"
					}
					  If ($eguide -eq $true){
						$sheet.cells.item($introw,6) = "Yes"
						}
					}
				}
			3 {
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "Bundle 1"
			    If ($bundle -contains "graph"){
			  	  $sheet.cells.item($introw,5) = "Yes"
				}
				  If ($eguide -eq $true){
				    $sheet.cells.item($introw,6) = "Yes"
				  }
				}
			##
			##	If the SAS 8 bundle can't be determined, SAS is probably installed incorrectly.
			##	Display the number of components installed and then list them.
			##
			Default {
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "There are $count components installed and they are $bundle ."
				If ($bundle -contains "graph"){
				  $sheet.cells.item($introw,5) = "Yes"
				}
				  If ($eguide -eq $true){
					$sheet.cells.item($introw,6) = "Yes"
					}
				}
		}
	}
	##
	##	If SAS version is not 8, it must be 9 as of 2009.  Analysis is the same as for version 8
	##
	Else {
	##
	##	If Graph is installed, it adds three components to the total.  Subtract
	##	3 components from $count to make the total components correct.
	  If ($bundle -contains "graph"){
		$count = $count - 3
		}
	    Switch ($count){
		  16 {If ($bundle -contains "fsp"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 9"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			  }
			}
		  14 {If ($bundle -contains "connect"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 8"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			  }
			}
		  13 {If ($bundle -contains "eis"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 7"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			  }
			}
		  11 {If ($bundle -contains "assist"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 6"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			  }
			}
		  10 {If ($bundle -contains "stat"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 5"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			  }
			Else {
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "Bundle 4"
				If ($bundle -contains "graph"){
				  $sheet.cells.item($introw,5) = "Yes"
				}
				  If ($eguide -eq $true){
					$sheet.cells.item($introw,6) = "Yes"
	 			  }
			}
		  }
		  8 {If ($bundle -contains "pcfile"){
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 3"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			}
			Else{
			  $sheet.cells.item($introw,1) = $computer
			  $sheet.cells.item($introw,2) = $user.username
			  $sheet.cells.item($introw,3) = $sasver
			  $sheet.cells.item($introw,4) = "Bundle 2"
				If ($bundle -contains "graph"){
				  $sheet.cells.item($introw,5) = "Yes"
				}
				  If ($eguide -eq $true){
					$sheet.cells.item($introw,6) = "Yes"
					}
			}
		  }
		  6 {
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "Bundle 1"
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
		  }
		  Default {
			$sheet.cells.item($introw,1) = $computer
			$sheet.cells.item($introw,2) = $user.username
			$sheet.cells.item($introw,3) = $sasver
			$sheet.cells.item($introw,4) = "There are $count components installed and they are $bundle ."
			  If ($bundle -contains "graph"){
				$sheet.cells.item($introw,5) = "Yes"
				}
				If ($eguide -eq $true){
				  $sheet.cells.item($introw,6) = "Yes"
				}
			}
		  }
	  }
	}
  ##
  ##	If machine doesn't respond to a ping, write the computer name and the no
  ##	response message.
  ##
  Else{
    $sheet.cells.item($introw,1) = $computer
    $sheet.cells.item($introw,4) = "Didn't respond to a ping."
  }
    ##
    ##	Increment the row counter for Excel
    ##
    $introw = $introw + 1
  }
  ##
  ##	Make the Excel columns autofit the data so nothing is cut off, save the
  ##	spreadsheet, and quit Excel.
  ##
  $workbook.entirecolumn.autofit()
  $worksheets.saveas("C:\bundlelist.xls")
  $excel.quit()
}
##
##	Execute the function
##
Bundles

Repair a Broken Symantec Management Client Service

As mentioned in a previous post, a SEP client can have a broken SMC service and you may very well never know that there is anything wrong (unless you use the script found in that post :-) ).  This is the Powershell script that will allow you to remotely repair most of those broken clients.  Again, as with the audit script, you must have admin rights to the computer that you are running this script on.  Since it uses WMI to run a remote process, WMI must also be working properly on the remote computer.

The script will repair one client at a time using the “-compname” parameter.  However, you can modify the script to read a text file by adding another “ForEach” and a “Get-Content” to the script.

#################################################################
#							        #
#- Antivirus SMC service repair for clients with corrupt        #
#     security policies.                                        #
#- Requires a commandline parm of -compname          		#
# - Parms can be changed to accept a list for multiple PCs	#
#								#
#- Written by Jim Melton					#
#								#
#################################################################
 
## Accept computer name from the commandline with -compname
param ([string]$CompName = $(throw "Text Input is Required"))
Write-Host $CompName
 
## Load the System.ServiceProcess .net class, set the Error Action to
## Sliently Continue, and initialize some variables.
[System.Reflection.Assembly]::LoadWithPartialName('system.serviceprocess')
$ErrorActionPreference = "Continue"
$SEPreg = "SOFTWARE\Symantec\Symantec Endpoint Protection\AV"
$SEPhome = "Home Directory"
$SEPinstall = ""
$DefPaths = "SOFTWARE\Symantec\SharedDefs"
$SMCkey = "SOFTWARE\Symantec\Symantec Endpoint Protection\SMC"
$SMCsvc = "Symantec Management Client"
$SEPnetwork = ""
 
## Create a new Pint object to test to see if the remote computer
## is online so the script doesn't bomb, assign -compname to a
## variable and ping the computer.
$ping = New-Object System.Net.NetworkInformation.Ping
$computer = $CompName
$reply = $ping.send($computer)
 
## If the computer replies, open a remote process on the broken computer,
## open the registry (HKLM in this case), and count the SEP registry entries
## to make sure SEP is actually installed.
If ($reply.status -eq 'Success'){
	$remoteProcess = [WmiClass]"\\$computer\ROOT\CIMV2:Win32_Process"
	$OpenRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$computer)
	$DefsKey = $OpenRegistry.OpenSubKey($DefPaths,$true)
	[int]$ValueCount = $DefsKey.ValueCount
 
	## If SEP is installed and has valid registry entries to get paths from,
	## find the SEP install path, write it to the screen, rename the existing
	## serdef.dat, copy a new serdef.dat, and restart the SMC service using
	## our remote WMI process.
	If ([int]$ValueCount -gt 0){
 
		If ($DefsKey.GetValueNames() -ne $null){
			$SEPpath = $OpenRegistry.OpenSubKey($SEPreg,$true)
			$SEPinstall = $SEPpath.GetValue("$SEPhome")
			$SEPtemp = $SEPinstall.length
			[int]$SEPtemp = [int]$SEPtemp - 3
			$SEPinstall = $SEPinstall.substring($SEPinstall.length - [int]$SEPtemp)
			Write-Host "Symantec Endpoint Protection is installed at $SEPinstall."
			Copy-Item "\\$computer\c$\$SEPinstall\serdef.dat" "\\$computer\c$\$SEPinstall\serdef.old" -Recurse
			$remoteProcess.Create("c:\$SEPinstall\smc.exe -start")
			Write-Host "Starting SMC service."
 
			Write-Host "Finished"
			}
		Else{
			Write-Host "SEP Install Not Found"
			}
	}
	Else{
		Write-Host "Not installed"
	}
}
 
Else{
	Write-Host "Computer is not responding to a ping"
#	}
}

Audit Symantec Endpoint Protection Clients

If you’ve ever administered clients running Symantec Endpoint Protection 11 (SEP), you know how woefully inadequate the SEP management interface is for determining the health of your clients.  If a computer get’s a corrupt policy, chances are the Symantec Management Client (SMC) service will stop and refuse to restart.  When this happens, the component of SEP responsible for updating definitions and reporting out of date definitions stops working.  The real-time scanning component – the Symantec Endpoint Protection service – will continue to run, but it will never update.  Since the service that tells the SEP Manager that a client is out of date doesn’t work, chances are you will never know about it unless one (or more) of your end users happens to report it.

So, in order to find the broken SEP clients in my company – about 14,000 computers in various locations around the country – I turned to Powershell and the .NET Framework to do the heavy lifting.  The results were both very surprising and disconcerting.  Hundreds of our computers were out of date and not reporting this to the SEP Manager.  This information allowed us to update our computers using a combination of some automation and the good ol’ fashioned Sneaker Net.

This script assumes a few things:

  1. You must have admin privileges on the computers you are auditing.
  2. File and Print Sharing is enabled on the computers.
  3. You have a pretty solid list of computer names to run the Powershell script against (I used an SCCM list of all discovered Active Directory computer objects).
#################################################################
#
#
#	- Antivirus Health Check Script
#	- Audit computer objects from a text file specified at
#		runtime.  Checks for AV version, stopped/running
#		services, and definition dates.
#
#	- Written by Jim Melton
#        ** Feel free to use and modify this script as needed.
#            Just please give credit if you do.
#
#################################################################
 
## Setup command line parameter to allow user to specify content
## file at runtime.  Parameter is -FileName
param ([string]$FileName = $(throw "Text Input is Required"))
Write-Host $FileName
 
## Load assembly used to interrogate services on the remote machine.
[System.Reflection.Assembly]::LoadWithPartialName('system.serviceprocess')
$erroractionpreference = "Continue"
 
## Set variables
$SMCsvc = "Symantec Management Client"
$SMCstatus = ""
$SEPsvc = "Symantec Endpoint Protection"
$SAVsvc = "Symantec Antivirus"
$SAV8svc = "Symantec Antivirus Client"
$Path = "c$\Program Files\Common Files\Symantec Shared\VirusDefs"
$SavePath = "\\SERVER\SHARE\"
 
## Instantiate Excel ComObject and write headings on spreadsheet.
$excel = New-Object -comobject Excel.Application
$excel.visible = $true
 
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Worksheets.Item(1)
 
$worksheet.Cells.Item(1,1) = "Machine Name"
$worksheet.Cells.Item(1,2) = "IP Address"
$worksheet.Cells.Item(1,3) = "AV Version"
$worksheet.Cells.Item(1,4) = "Virus Definition"
$worksheet.Cells.Item(1,5) = "Status"
 
$range = $worksheet.UsedRange
$range.Interior.ColorIndex = 19
$range.Font.ColorIndex = 11
$range.Font.Bold = $True
 
$intRow = 2
 
## Create Ping object
$ping = New-Object System.Net.NetworkInformation.Ping
 
## Get data from user specified file
$colComputers = get-content $FileName 
 
foreach ($computer in $colComputers){
 
	## Zero variables at the top of the loop and ping the next machin
	## in the list.
	$IPaddress = ""
	$AVservice = ""
	[string]$service = ""
	$VirDate = ""
	$reply = $ping.send($computer)
	$IPaddress = [string]$reply.Address
 
	## Check ping reply.  If it's a success, test the Symantec Shared files path to see if
	## it exists.  If the path is valid, get the content of definfo.dat and parse the
	## definition date and rev.
	If ($reply.status -eq 'Success'){
		$ValidPath = Test-Path "\\$computer\$path"
		If ($ValidPath -eq "True"){
		     $DefInfo = Get-Content "\\$computer\$Path\definfo.dat"
		     $DefLine = $DefInfo[1]
		     $DefYear = $DefLine.substring(8,4)
		     $DefMonth = $DefLine.substring(12,2)
		     $DefDay = $DefLine.substring(14,2)
		     $Revision = $DefLine.substring(17,3)
		     [int]$DefDate = $DefLine.Substring(8,8)
		     [string]$DefDate = "$DefMonth" + "-" + "$DefDay" + "-" + "$DefYear"
		     $VirDate = [datetime]$DefDate
		}
 
		## If the path or definfo.dat doesn't exist, bail out of the if statement.
		Else{
		     $VirDate = $null
		     Continue
		}
	## Query the remote machine for the SEP, SAV, or SAV 8x RTVScan services.  Also,
	## query the SMC service for SEP.  These are separate in order to make sure
	## that both the SEP and SMC services are running on a machine.
	$services = [System.ServiceProcess.ServiceController]::GetServices($computer) | `
			where { (($_.displayname -eq $SEPsvc) -or ($_.displayname -eq $SAVsvc) `
			                                      -or ($_.displayname -eq $SAV8svc))}
	$SMCservice = [System.ServiceProcess.ServiceController]::GetServices($computer) | `
			where { ($_.displayname -eq $SMCsvc)}
 
	## Check to see what services are present.  If there are no services on the machine, write that
	## into the spreadsheet and continue on to the next computer.
	foreach ($service in $services){
		If ($service.displayname -eq $null){
			$worksheet.Cells.Item($intRow,1) = $computer
			$worksheet.Cells.Item($intRow,2) = $IPaddress
			$worksheet.Cells.Item($intRow,5) = "Services are not present on $computer."
			$intRow = $intRow + 1
		}
 
		## Otherwise, if it's SAV or SAV 8, insert SAV into $AVService.
		Else{
			If (($service.DisplayName -eq $SAVsvc) -or ($service.DisplayName -eq $SAV8svc)){
				$AVservice = "Symantec Antivirus"
			}
 
			## If not Null or SAV, then it must be SEP.  Insert SEP into $AVService.  Also check to see if
			## the SMC service is stopped.  If so, set $SMCstatus to Stopped.
			Else {
				If ($service.DisplayName -eq $SEPsvc){
				     $AVservice = "Symantec Endpoint Protection"
				     If ($SMCservice.DisplayName -eq $SMCsvc){
		        	          If ([string]$SMCservice.status -eq 'Stopped'){
			        		$SMCstatus = "Stopped"
					  }
				     }
				}
			}
 
		## If computer is online and has AV installed, insert its information into the spreadsheet.
		## If the SMC service is stopped, append that info to the Status block showing the SEP
		## RTVScan status.  Otherwise,just insert the status of RTVScan.
		$worksheet.Cells.Item($intRow,1) = $computer
		$worksheet.Cells.Item($intRow,2) = $IPaddress
		$worksheet.Cells.Item($intRow,3) = $AVservice
		$worksheet.Cells.Item($intRow,4) = "$DefDate" + " rev " + "$Revision"
		If ($SMCstatus -eq "Stopped"){
			$worksheet.Cells.Item($intRow,5) = [string]$service.Status + ", `
				however, the SMC Service is stopped."
		}
		Else{
			$worksheet.Cells.Item($intRow,5) = [string]$service.Status
		}
	}
 
	## Increment the spreadsheet row and reset $SMCstatus to blank.
	$intRow = $intRow + 1
	$SMCstatus = ""
		}
	}
 
	## If the machine doesn't respond to a ping, insert that in the spreadsheet.
	Else{
		If ($reply.status -eq "TimedOut"){
			$worksheet.Cells.Item($intRow,1) = $computer
			$worksheet.Cells.Item($intRow,3) = $IPaddress
			$worksheet.Cells.Item($intRow,5) = "Did not respond to a ping."
			$intRow = $intRow + 1
		}
		## If the machine doesn't respond to a ping and the IP is not valid, the computer name
		## must not bevalid any longer.
		Else{
			$IPaddress = "Computer name is not valid."
			$worksheet.Cells.Item($intRow,1) = $computer
			$worksheet.Cells.Item($intRow,3) = $IPaddress
			$worksheet.Cells.Item($intRow,5) = "Did not respond to a ping."
			$intRow = $intRow + 1
		}
	}
 
## Once the data is completely written to the spreadsheet, auto-fit all columns to the data width.
$range.EntireColumn.AutoFit() | Out-Null
}
 
## Switch statement using a regular expression switch to determine location being audited based on the
## input file filename.  If you need an Adhoc report, use computers.txt as the input file.
Switch -regex ($FileName){
 
	"loc01comps.txt" {$Location = "Location1"}
	"loc02comps.txt" {$Location = "Location2"}
	"loc03comps.txt" {$Location = "Location3"}
	"loc04comps.txt" {$Location = "Location4"}
	"loc05comps.txt" {$Location = "Location5"}
	"loc06comps.txt" {$Location = "Location6"}
	"loc07comps.txt" {$Location = "Location7"}
	"loc08comps.txt" {$Location = "Location8"}
	"loc09comps.txt" {$Location = "Location9"}
	"loc10comps.txt" {$Location = "Location10"}
	"loc11comps.txt" {$Location = "Location11"}
	"computers.txt"    {$Location = "Adhoc Report"}
	default {$Location = "Other"}
}
 
## Save the spreadsheet to the path in the variable using the name determined from the above Switch.
$SpreadSheetName = $SavePath+$Location+".xls"
$workbook.Worksheets.item(1).Name = $Location
 
## Check to see if the spreadsheet exists.  If it does, delete it and recreate it, and save it.
## Attempt to close Excel and end the ComObject.  This doesn't seem to work well with Excel 2K7,
## so we are killing processes called Excel.  This will close all Excel processes.
If(Test-Path $SpreadSheetName){
	Remove-Item $SpreadSheetName
	Write-host $SpreadSheetName
	$excel.ActiveWorkbook.SaveAs($SpreadSheetName)
	$excel.Quit()
	[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
	Remove-Variable excel
	Get-Process -Name Excel | Kill
}
Else{
	$excel.ActiveWorkbook.SaveAs($SpreadSheetName)
	$excel.Quit()
	[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
	Remove-Variable excel
	Get-Process -Name Excel | Kill
}