Opposite of Serious

A random hodgepodge of IT topics and thoughts

We’ve moved!

Come join me over at http://www.oppositeofserious.com where I am currently in the process of setting this blog up with some additional items! I won’t be updating this blog anymore, and once everything is migrated I will probably just have this post as a modified landing page.

Remotely Install Software Using WMI And Powershell

Today I’d like to talk about remote process creation using Powershell and WMI. Specifically for software installation, but you could start any process on a remote machine by modifying the code to your liking. As always, I’m sure there is a better way to do this, but this is the way I’ve found to make everything work. I wrote this because we had a large group of machines that needed the same thing installed and we didn’t have authorization to do it any way other than manually (yuk!) or via script. If you’re not using a MSI install OR an EXE that has a silent install trigger you are going to need to wrap it up in a silent install package. I’ll cover that in another post another day and update this with a link to it in case you need to know how.

If you need to start a local process powershell comes with a built in way to accomplish that. Start-Process will start any process locally. However, if you need to start a process remotely Start-Process will leave you wanting. The way I’ve chosen to start the remote process to install a piece of software is by using win32_process. You can call win32_process using the Invoke-WmiMethod cmdlet inside powershell to invoke the create method and pass it arguments. If that’s the only part you need I’ll save you further reading and provide a snippet of it here. The specific line of code looks something like this.


Invoke-WMIMethod win32_process -name create -ArgumentList 'msiexec.exe /i "c:\path\to\file.msi" /qb REBOOT=ReallySuppress'

Alright, so that line will execute the file.msi installer located in c:\path\to\ on the TARGET machine. This means we will need to copy the file to the target machine. I typically use the temp directory and then clean the file when the install is done. I do this by monitoring the process that gets created to see when it ends and the file can be deleted, but lets not get ahead of ourselves.

So, we start off with the list of workstations that will be imported and then have the install ran against each machine in the list using a foreach loop.


$colComputer = Get-Content c:\targets.txt

Foreach ($strComputer in $colComputer) {

Now that we have our workstation list together and ready for processing we will need to verify that the RemoteRegistry service is running or we won’t be able to monitor the process creation. If your environment always has this enabled then you can skip the next piece of the script, but it doesn’t hurt anything to leave it in as it will only start the service if it is found to be stopped.


$service = Get-Service 'RemoteRegistry' -cn $strComputer

If ($service.Status -match 'Stopped') {

    $service.Start()

}

Now, we could also use WMI here if we felt like it. Just for comparison here is the code to use WMI instead

$service = gwmi win32_service -cn $strComputer | Where { $_.name -match 'RemoteRegistry' }

If ($service.state -match 'Stopped') {

    $service.StartService()

}

Notice the differences. Using the Get-Service cmdlet it’s the status that tells you if the service is running or stopped, versus win32_service it is the state that designates whether the service is running. The message is the same with both however, they will either be “Running” or “Stopped”. To start or stop the service with the Get-Service cmdlet you need to call the Start() method or the Stop() method. With win32_service you have to StartService() or StopService(). If you forget the proper method you can always pipe the command to Get-Member to be shown a list of Methods and Properties for the given item. You can pipe variables that contain the results of a cmdlet or WMI call to Get-Member as well, like so.


$service = Get-Service RemoteRegistry -cn $strComputer

$service | Get-Member

OK, so we have our list of machines, we’ve initiated our Foreach loop, and we’ve taken care of the RemoteRegistry service. Next we will cover getting our installation files onto the target machine. I’m assuming that your piece of software has more than one file, if you only have one install file you can remove the foreach loop and just directly copy the file.


#If the target directory doesn't exist, create it

If (!(Test-Path \\$strComputer\c$\temp\MyInstall\)) {

mkdir \\$strComputer\c$\temp\MyInstall\

}

#Get all the files for the setup program. This can be on a network share or your local machine.

$files = Get-ChildItem "\\path\to\software\"

Foreach ($file in $file) {

Copy-Item $file.fullname "\\$strComputer\c$\temp\MyInstall\"

}

The next step is to create a variable with our process. Remember, the path you put to the file needs to point to an existing file on the target workstation.


#Start the process on the remote machine

$process = Invoke-WMIMethod win32_process -name create -cn $strComputer -ArgumentList 'msiexec /i "c:\temp\MyInstall\install.msi" /qb /norestart'

#Get the process ID for the newly created process

$id = $process.processid

#Create a DO WHILE loop to monitor for process exit

$a = ""
do {
$a = Get-Process -cn $strComputer | Where{$_.ID -match "$ID"}
}
While ($a -ne $null)

#Delete the MyInstall directory on the remote machine
del \\$strComputer\c$\temp\MyInstall -force -recurse

#Lets leave the machine the way we found it in regards to running services
If ($service.status -match 'Stopped') {</pre>
<pre>$service.Stop()
}

That’s pretty much all there is to it. You could put in a check to verify that the software is installed by reading the registry, or checking for a file that is created upon installation after this if you wanted to. If you’re upgrading software then I recommend checking the registry key to do a version match instead of verifying that the install path has the programs files. Here is everything we have written in pieces combined into the final script. Remember, you will have to run your powershell terminal or ISE using an account that has admin rights on the target workstations.

$colComputer = Get-Content c:\targets.txt

Foreach ($strComputer in $colComputer) {

 $service = Get-Service 'RemoteRegistry' -cn $strComputer

 If ($service.Status -match 'Stopped') {
 $service.Start()
 }

 #If the target directory doesn't exist, create it
 If (!(Test-Path \\$strComputer\c$\temp\MyInstall\)) {
 mkdir \\$strComputer\c$\temp\MyInstall\
 }

 #Get all the files for the setup program. This can be on a network share or your local machine.
 $files = Get-ChildItem "\\path\to\software\"

 Foreach ($file in $file) {
 Copy-Item $file.fullname "\\$strComputer\c$\temp\MyInstall\"
 }

 #Start the process on the remote machine
 $process = Invoke-WMIMethod win32_process -name create -cn $strComputer -ArgumentList 'msiexec /i "c:\temp\MyInstall\install.msi" /qb /norestart'

 #Get the process ID for the newly created process
 $id = $process.processid

 #Create a DO WHILE loop to monitor for process exit
 $a = ""

 do {
 $a = Get-Process -cn $strComputer | Where{$_.ID -match "$ID"}
 }

 While ($a -ne $null)

 #Delete the MyInstall directory on the remote machine
 del \\$strComputer\c$\temp\MyInstall -force -recurse

 #Lets leave the machine the way we found it in regards to running services
 If ($service.status -match 'Stopped') {
 $service.Stop()
 }
}

As always, I welcome comments and suggestions on how to improve the blog and what might be useful for future articles. Have an idea, suggestion, or correction? Please leave a comment!

Gathering Information About a Remote Workstation Using Powershell

Do you ever wish you could quickly and easily look up or verify information about a remote workstation? Well using Powershell, WMI, and a little .Net now you can! Follow along with me as we construct a script for gathering some information about a remote workstation.

Note: If you have never used powershell on your machine before you are going to need to run the powershell terminal as an administrator and then type the following and hit enter. This will allow you to run powershell scripts on your workstation after you hit Y to confirm you want to set that execution policy. You can read more about execution policies and the Set-ExecutionPolicy command here. You do not need to do this on target machines, but you WILL need to have your powershell terminal running under an account that has administrative rights on the machines you are running your scripts against. 


Set-ExecutionPolicy RemoteSigned

 

Now onto business! We are going to write our script using the Powershell ISE (Integrated Scripting Environment), so go ahead and fire it up. You’ll be presented with the following:

Powershell ISE

So, how do we want to get the target machine from the user? We could use good old Read-Host assigned to a variable, but I prefer to use a mandatory parameter that the user uses when they call the script. If they omit it, the script will then ask for the missing information. We do this by including the following at the very beginning of the script:

param(
[Parameter(Mandatory=$true)]
[alias("cn")]
[string]$ComputerName
)

The [alias(“cn”)] line will allow the user to use either -ComputerName OR -cn to designate the target workstation. You can make the alias for the parameter whatever you like or omit it altogether.

Now, what kind of information are we likely to want from a remote workstation? My list includes things like the OS, model, serial number, BIOS version, what kind of processor it has, total system memory, IP address, and MAC address. Your list might have more or less, but this is what we will base our information gathering script on. Feel free to dig into the WMI documentation for other things you might want to include and try adding them to your version of the script.

param(
[Parameter(Mandatory=$true)]
[alias("cn")]
[string]$ComputerName
)

CLS
Write-Host "Operating System:" (gwmi win32_operatingsystem -ComputerName $ComputerName).Caption
Write-Host "Model:" (gwmi win32_ComputerSystem -ComputerName $ComputerName).Model
Write-Host "Serial:" (gwmi win32_SystemEnclosure -ComputerName $ComputerName).SerialNumber
Write-Host "BIOS Version:" (gwmi win32_BIOS -ComputerName $ComputerName).SMBIOSBIOSVersion
Write-Host "Processor:" (gwmi win32_Processor).Name
Write-Host "System Memory:" ([math]::truncate((gwmi win32_ComputerSystem -ComputerName $ComputerName).TotalPhysicalMemory / 1GB))"GB"
Write-Host "IP Address:" ([system.net.dns]::Resolve($ComputerName)).AddressList.IPAddressToString
Write-Host "MAC Address:" (gwmi win32_NetworkAdapter | WHERE NetConnectionID='Local Area Connection').MACAddress

We are using WMI for everything but the IP address. To easily get that we will use a little bit of .NET. Read more about the system.net.dns class here. You can use .net by wrapping it in brackets [] so feel free to experiment with them in your powershell scripts as long as you have NET Framework installed on the target machines.

Once you are satisfied with the way it runs (F5 in the ISE to run and test it) save the file as a .ps1. When you are ready to use it you will simply open powershell, browse to the location of the file with cd c:\path\to\script\ and then run it against a target by typing script_name.ps1 -ComputerName TargetMachine or script_name.ps1 -cn TargetMachine (you did remember the alias bit, right?). If you want to be really fancy, you could put a help file inside your script. How you might ask? Why, it’s easy. Simply edit your script to add the following to the very beginning.


<#
 .SYNOPSIS
Gather information from target workstation
 .DESCRIPTION
 This is a wrapper function that gathers information about a target PC
 .PARAMETER ComputerName
 The name of the Computer you want the script to run against
 .EXAMPLE
 EventLog -ComputerName SomeComputer
 Runs the script against the workstation SomeComputer
.EXAMPLE
 Eventlog -cn SomeOtherPC
 Runs the script against the workstation SomeOtherPC using the -cn alias for -ComputerName
 .LINK
 oppositeofserious.wordpress.com
 #>

Adding that to the beginning of your script will allow users to use the built-in Get-Help function to get help on your script. It will look like the following if you use exactly what I have typed for each section.

Gather-Information

They can use Get-Help Gather-Information.ps1 -examples to be given every example and description you have added, so add as many examples as you feel necessary.

Put it all together and your script should look something like this.


<#
.SYNOPSIS
Gather information from target workstation
.DESCRIPTION
This is a wrapper function that gathers information about a target PC
.PARAMETER ComputerName
The name of the Computer you want the script to run against
.EXAMPLE
EventLog -ComputerName SomeComputer
Runs the script against the workstation SomeComputer
.EXAMPLE
Eventlog -cn SomeOtherPC
Runs the script against the workstation SomeOtherPC using the -cn alias for -ComputerName
.LINK
oppositeofserious.wordpress.com
#>

param(
[Parameter(Mandatory=$true)]
[alias("cn")]
[string]$ComputerName
)

CLS
Write-Host "Operating System:" (gwmi win32_operatingsystem -ComputerName $ComputerName).Caption
Write-Host "Model:" (gwmi win32_ComputerSystem -ComputerName $ComputerName).Model
Write-Host "Serial:" (gwmi win32_SystemEnclosure -ComputerName $ComputerName).SerialNumber
Write-Host "BIOS Version:" (gwmi win32_BIOS -ComputerName $ComputerName).SMBIOSBIOSVersion
Write-Host "Processor:" (gwmi win32_Processor).Name
Write-Host "System Memory:" ([math]::truncate((gwmi win32_ComputerSystem -ComputerName $ComputerName).TotalPhysicalMemory / 1GB))"GB"
Write-Host "IP Address:" ([system.net.dns]::Resolve($ComputerName)).AddressList.IPAddressToString
Write-Host "MAC Address:" (gwmi win32_NetworkAdapter | WHERE NetConnectionID='Local Area Connection').MACAddress

 

This is just how I would accomplish looking for that information, but there are a number of different ways you could put this script together. If you prefer to do it another way then I encourage you to leave a comment and start a discussion.

 

As an aside, I welcome comments and suggestions on how to improve the blog and what might be useful for future articles. Have an idea, suggestion, or correction? Please leave a comment!

Using Powershell to Administer Printers

Have you ever needed to restart the print spooler service, check the print queue, and verify proper settings for a desktop printer on a remote workstation in a Microsoft Windows environment? If you said yes and you’re not using powershell to do these tasks then follow along while we explore just a few of the awesome administrative tasks that powershell is capable of. Get-WMIObject (or gwmi for short) is how you interact with WMI from within Powershell. WMI (Windows Management Instrumentation) has instructions for a wide range of administrative tasks. Today we will be talking about printer management on remote machines, with a little service management thrown in for good measure. First things first, you need to open powershell, so go ahead and press Ctrl + R to bring up a run dialog, type in powershell and hit enter. You should be presented with a screen that looks something like the following.

Image

Powershell accepts all DOS commands as well as it’s own scripting language. Lets start by looking at printers locally. Type Get-WMIObject win32_printer to get a list of printers installed on your computer. As you can see I only have the Microsoft Office printers installed on my computer so far.

Image If you only wanted the list of printers installed you could pipe the results from your WMI query to win32_printer to Select-Object Name like so:

Get-WmiObject win32_printer -ComputerName WorkstationName | Select-Object Name

If you have a job stuck in a print queue you can use win32_printjob to see what all is printing, like so.

Get-WmiObject win32_printjob -ComputerName WorkstationName

If you have jobs stuck in the queue that you need to clear out, simply pipe them all to the delete method of win32_printjob.

Get-WmiObject win32_printjob -ComputerName WorkstationName | Foreach-Object { $_.delete() }

What if the spooler is the problem you might ask? There is a WMI function for service management as well, or you could use powershells built in service management. The WMI way:

Get-WmiObject win32_service -ComputerName WorkstationName | Where  $_.name -match 'spooler' }

The built in way:

Get-Service Spooler -ComputerName WorkstationName

Either way you want to go about it is fine. I prefer the WMI method as it usually has more methods and properties as shows in the following Get-Member screenshots.

Get-Service | Get-Member

Powershell


Get-WmiObject win32_service | Get-Member

Powershell     You could put together a script that clears the print queue of every printer and then restarts the print spooler service pretty easily. Something along the lines of the following would work.


$strComputer = Read-Host "What is the name of the computer?"
Get-WmiObject win32_printjob -cn $strComputer | ForEach-Object { $_.delete() }
$service = get-service -name spooler -ComputerName $strComputer
Restart-Service -inputobj $service -force

I hope his has been a useful primer in printer management using powershell and WMI.

Additional reading from the Microsoft Developer Network:

WMI Reference

WMI Printing Classes

Win32_Service

Powershell 99 bottles of beer

This is my submission for http://99-bottles-of-beer.net/ in the powershell category. The site uses the song 99 bottles of beer to show code examples of 1500 languages and variations according to the website. Very cool, lets see if they like my simple implementation.

CLS
$i = (99..1)
Foreach ($b in $i) {
    If ($b -ge 2) {
        Write-Host $b "bottles of beer on the wall," $b "bottles of beer."
        Write-Host "Take one down, pass it around,"($b - 1)"bottles of beer on the wall"
        Write-Host
    }
    Else {
        Write-Host $b "bottle of beer on the wall," $b "bottle of beer."
        Write-Host "Take one down, pass it around, no more bottles of beer on the wall"
        Write-Host
        Write-Host "No more bottles of beer on the wall, no more bottles of beer. "
        Write-Host "Go to the store and buy some more, 99 bottles of beer on the wall."
    }
}

Hello there

Welcome to the opposite of serious. This will be a landing pad for things I find interesting and random thoughts that strike me during the day.