A Prompt Response

Yesterday, fellow blogger and Powershell guru, Jeff Hicks posted an article about how to do some neat things with your Powershell prompt.  (You can find his article here.)

To add to his entry, I would like to share how I create my Powershell prompt.  I actually have three functions in my profile to get the work done.  They are rather small functions, but by splitting it up, it makes things a little easier to follow.

The first part of my prompt script actually doesn’t change the prompt itself.  However, it does run every time I submit a command so that the title bar is updated.  Since I sometimes forget to reboot my computer on a regular basis (yes, I do run updates!), it is helpful to know how long it has been since the computer was rebooted.  This first function calculates and returns a string to tell me that information.

function get-uptime
{
$lastBootTime = [Management.ManagementDateTimeConverter]::ToDateTime((Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime)
$duration = New-TimeSpan -Start $lastBootTime -End (get-date)
return "{0} days {1} hours {2} minutes {3} seconds" -f $($duration.Days), $($duration.Hours), $($duration.Minutes), $($duration.Seconds)
}

This command simply uses the LastBootUpTime property from the WMI class Win32_OperatingSystem and returns a human-readable string.

The second function accesses the Win32_Battery class to get the EstimatedChargeRemaining property.  Based on the charge percentage left, the function returns a color to be used in the prompt function.  I have used four colors to indicate a full charge, 50% charge, 25% charge, and less than 25% charge.

function Get-LaptopBatteryStatus
{
$charge = (Get-WmiObject -Class Win32_Battery).EstimatedChargeRemaining
if ($charge -ge 100)
{ return "White" }
if ($charge -ge 50)
{ return "Green" }
if ($charge -ge 25)
{ return "yellow" }
return "red"
}

Now to bring it all together.  The final function actually creates the prompt and runs these other functions to make the magic happen.  Every time I enter a command, the prompt function updates the title bar and changes the color of the prompt to indicate how much battery I have left.  It also shows me the username and computer name that I am using, which is helpful if I am logged in as multiple users (with different admin levels) to know which console to activate.  The battery indicator is not meant to be a precise
indicator, but it does alert me in plenty of time that I need to get to an outlet for some more juice.

function prompt
{
$host.ui.rawui.WindowTitle = "{0} - {1} - {2} - Uptime: {3}" -f $ENV:username, $env:COMPUTERNAME, $(get-date -Format MM/dd/yyyy), $(get-uptime)
Write-Host
Write-Host "PS $(get-location)> " -NoNewline -ForegroundColor $(Get-LaptopBatteryStatus)
return " "
}

PowershellPromptImage
The nice thing about Powershell is that you can incorporate as many changes like this as you feel necessary to get your work done.  When you get a chance, head over to Jeff Hicks’ blog to get some more ideas on how Powershell can help you.

Posted in Beginner, Scripting | Tagged , , , , , | Comments Off on A Prompt Response

Clearing Your Akamai CDN Cache with Powershell – Redux

About a year ago, I wrote an entry concerning clearing your Akamai Content using Powershell and the SOAP API they published then.  I have noticed that article has been getting some additional love recently.  Since it has surged in popularity, I thought I would offer an updated version of the script.  Akamai has changed the API to use a REST API now, which makes it a little simpler to use Powershell…but also throws a little gotcha out there too.  As a side note, Akamai will no longer support the older SOAP API as of June 1, 2014.  The method below is available now and will need to be used after that date. Continue reading

Posted in Advanced Function, Cmdlet Help, PSObjects, Scripting | Tagged , , | Comments Off on Clearing Your Akamai CDN Cache with Powershell – Redux

Check out my “Hey, Scripting Guy!” Blog Post

Today, the “Hey, Scripting Guy!” blog has featured an article that I wrote about using Powershell with Windows event logs.  Be sure to go check out the article at the link below.

http://blogs.technet.com/b/heyscriptingguy/archive/2013/06/20/how-to-use-powershell-to-write-to-event-logs.aspx

Posted in Event Logs, Scripting | Comments Off on Check out my “Hey, Scripting Guy!” Blog Post

Clearing Your Akamai Content Delivery Network Cache with Powershell

The API referenced in this article will expire on June 1, 2014.  Please click here to go to the updated article on this information.

Recently, I had to do some work on a project with Akamai caching.  Part of that project required writing some C# code for a service to clear parts of the cache.  As I was working on that and looking around the Internet, I noticed that there were no examples of how to leverage Powershell to perform this task.  The documentation from Akamai showed alternate ways to connect, but these were using either Perl, Javascript, or some flavor of Visual Basic.  Note: You must have an account with Akamai in order to use this script.

Continue reading

Posted in Advanced Function, Cmdlet Help, Scripting | Tagged , , | Comments Off on Clearing Your Akamai Content Delivery Network Cache with Powershell

Advanced Event 3 – 2013 Scripting Games

We have made it to the midpoint of the Scripting Games.  I think that a lot has been learned and there is still more to learn.  This is really the whole reason for the games; to get people to learn something new about Powershell.  Let’s see what was asked for in Event 3, A Disk Decision. Continue reading

Posted in Scripting Games 2013 | Tagged , , , , , , , | Comments Off on Advanced Event 3 – 2013 Scripting Games

Advanced Event 2 – 2013 Scripting Games

The games continue! The second event has closed and voting has begun. There is still time for you to jump in and try your hand at learning Powershell. There is some really good feedback to be had to sharpen your skills.

The second event is based on gathering information about servers that might be eligible for upgrade. You can see the full script requirements for the Advanced track here.

As public voting continues, here is my solution and how I got to this conclusion.

Function Get-ServerReport
{
<#
	.SYNOPSIS
		Gather computer system information.

	.DESCRIPTION
		This script will connect to specified computers and gather:
			*Computer Name
			*Windows Version
			*Physical Memory
			*Number of Processors

	.PARAMETER  ComputerName
		Specifies the target computer for the report operation. The value can be a fully qualified domain name, a NetBIOS name, or an IP address. To specify the local
		computer, use the local computer name, use localhost, or use a dot (.) . The local computer is the default. When the remote computer is in a different domain from the
		user, you must use a fully qualified domain name. You can also pipe this parameter value to the Get-ServerReport function.

	.PARAMETER  Credential
		Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as "User01", "Domain01\User01", or
		User@Contoso.com. Or, enter a PSCredential object, such as an object that is returned by the Get-Credential cmdlet. When you type a user name, you are prompted for a
		password.

	.EXAMPLE
		PS C:\> Get-ServerReport

		This command will return the items from the local computer, which is the default value for ComputerName

	.EXAMPLE
		PS C:\> Get-ServerReport -ComputerName Server01

		PhysicalMemory ComputerName									Processors                                                 WindowsVersion
		-------------- ------------									----------                                                 --------------
		    2146897920 Server01										2                                                          Microsoft(R) Windows(R) Server 2003, Standard Edition

		This command connects and reports on Server01.  This assumes that the currently logged on user has access to the specified server.

	.EXAMPLE
		PS C:\> $credential = Get-Credential CONTOSO\jdoe
		PS C:\> Get-ServerReport -ComputerName Server01, Server02 -Credential $credential

		PhysicalMemory ComputerName												Processors                                                 WindowsVersion
		-------------- ------------												----------                                                 --------------
			2146897920 Server01													2                                                          Microsoft(R) Windows(R) Server 2003, Standard Edition
			4294135808 Server02													2                                                          Microsoft(R) Windows(R) Server 2003, Standard Edition

		In this example, two servers are queried and reported.  The command is given the specific credential object to allow connection to the remove servers.

	.EXAMPLE
		PS C:\> Get-Content -Path C:\data\servers.txt | Get-ServerReport

		PhysicalMemory ComputerName										Processors                                                 WindowsVersion
		-------------- ------------										----------                                                 --------------
			2146897920 Server01											2                                                          Microsoft(R) Windows(R) Server 2003, Standard Edition
			4294135808 Server02											2                                                          Microsoft(R) Windows(R) Server 2003, Standard Edition
			8589467648 Server03											2                                                          Microsoft Windows Server 2008 R2 Standard
			8589467648 Server04											2                                                          Microsoft Windows Server 2008 R2 Standard
			8466395136 Server05											1                                                          Microsoft Windows 7 Enterprise
					   Server06																									   ** OFFLINE **

		In this example, a list of servers stored in the file at C:\data\servers.txt, is read and piped to the Get-ServerReport function.  Server06 could not be contacted for some reason so
		the script reports "** OFFLINE **" in the WindowsVersion property.

	.INPUTS
		System.String[], System.Management.Automation.PSCredential

	.OUTPUT
		System.Management.Automation.PSCustomObject

	.NOTES
		Instead of generating an error report or breaking the flow of the data, the script will "inline" an error condition in the WindowsVersion property.  If the server is offline (** OFFLINE **)
		or access is denied (** ACCESS DENIED **), the indication will be displayed in that column.  The Processors and PhysicalMemory properites will be empty in that case.
#>

	[CmdletBinding()]
	Param(
		[Parameter(ValueFromPipeline=$true,
				   ValueFromPipelineByPropertyName=$true)]
		[String[]]$ComputerName = $ENV:COMPUTERNAME,
		[Parameter(Mandatory=$false)]
		[System.Management.Automation.PSCredential]$Credential
	)

	BEGIN
	{

	}

	PROCESS
	{
		foreach($computer in $ComputerName)
		{
			Write-Debug "Processing $computer"
			$properties = @{
				ComputerName=$computer
				WindowsVersion=""
				PhysicalMemory=""
				Processors=""
			}

			if (Test-Connection -ComputerName $computer -Count 1 -Quiet)
			{
				Write-Debug "$computer is online."
				New-Variable -Name os -Force
				New-Variable -Name computerSystem -Force
				New-Variable -Name processor -Force

				Write-Debug "Variables created."
				try
				{
					if ($credential -and ($computer -ne $ENV:COMPUTERNAME))
					{
						Write-Debug "Credential object supplied. Not local computer."
						$os = Get-WmiObject -ComputerName $computer -Class Win32_OperatingSystem -Property Caption -Impersonation Impersonate -Authentication PacketPrivacy -Credential $credential
						$computerSystem = Get-WmiObject -ComputerName $computer -Class Win32_ComputerSystem -Property TotalPhysicalMemory -Impersonation Impersonate -Authentication PacketPrivacy -Credential $credential
						$processor = @(Get-WmiObject -ComputerName $computer -Class Win32_Processor -Impersonation Impersonate -Authentication PacketPrivacy -Credential $credential)
					}
					else
					{
						Write-Debug "Credential object not supplied or accessing local computer."
						$os = Get-WmiObject -ComputerName $computer -Class Win32_OperatingSystem -Property Caption -Impersonation Impersonate -Authentication PacketPrivacy
						$computerSystem = Get-WmiObject -ComputerName $computer -Class Win32_ComputerSystem -Property TotalPhysicalMemory -Impersonation Impersonate -Authentication PacketPrivacy
						$processor = @(Get-WmiObject -ComputerName $computer -Class Win32_Processor -Impersonation Impersonate -Authentication PacketPrivacy)
					}
					Write-Debug "Assigning $($os.Caption) to custom object."
					$properties.Set_Item("WindowsVersion", "$($os.Caption)")
					$properties.Set_Item("PhysicalMemory", $($computerSystem.TotalPhysicalMemory) -as [Int64])
					$properties.set_Item("Processors",$($processor.Count) -as [int])
				}
				catch [System.UnauthorizedAccessException]
				{
					Write-Debug "Encountered Access Denied error, tagging object."
					$properties.set_Item("WindowsVersion", "** ACCESS DENIED **")
				}
			}
			else
			{
				$properties.set_Item("WindowsVersion","** OFFLINE **")
			}

			Write-Output -InputObject (New-Object -TypeName PSObject -Property $properties)

		}
	}

	END
	{}
}

I am not going to discuss the use of comment-based help for this script. It is there, and if you want to read a really good article on the how’s and why’s of comment-based help, you can check out the official documentation.

The requirements of the advanced event stated we need to be able to pipe computer names into the script. On lines 77-79, you can see where the parameter is declared. Learning from my mistake on the last event, I chose the standard parameter name “ComputerName” for the parameter. Note that I could have (and maybe should have) added an alias tag to allow for other property names that might contain the server name, such as “cn,” “server,” etc. That would allow for more compatibility with other cmdlets that might output the computer name under a different property name.

One additional note, you might notice that the ComputerName parameter is declared as a string array ([string[]]). Doing this allows you to handle input in a variety of ways. The script can handle a single server name passed in with the -ComputerName parameter:

Get-ServerReport -ComputerName Server01

You can pass in a string array of server names:

$servers = Get-Content -Path C:\Temp\Servers.txt
Get-ServerReport -ComputerName $servers

Or you can pipe server names out of another cmdlet:

get-adcomputer -filter * | Get-ServerReport

The second parameter is something I consider to be a best practice. You should be running your computer with an account that has least necessary privileges. (You are, aren’t you?) That means that your user account probably doesn’t have access to the servers that you are trying to interrogate. The credential parameter allows you to pass in a PSCredential object with your admin account.

$cred = get-credential -Username CONTOSO\jdoe
$servers | Get-ServerReport -Credential $cred

Now we get into doing some work with the information we do have. When you start dealing with pipeline input, you have to have a BEGIN/PROCESS/END block. Any code you put in the BEGIN block will be processed one for each time the script is run. This is used to set up a log file, or connections to a database, things like that.

The PROCESS block takes each object that comes in from the pipeline and, well, processes it. This block will run for each object that comes in. There is something interesting that confuses new users to Powershell. If the PROCESS block runs once for each item that comes in, then why do I have a foreach loop inside the PROCESS block? Won’t that handle each of the items? Actually, no. The foreach block is in there on the chance that an array is passed in. If one item is passed in, the foreach will run only once…for the one item that comes in. If an array gets passed, the foreach will enumerate each item in the array to process. So, when pipeline input is received, each object coming in is counted as a single item. Each item causes the PROCESS block to run, and the foreach will iterate once for a single-item array.

Inside the foreach loop, I begin gathering information about each computer and assembling a custom object to be output. I start by creating a hash table to hold the properties I am collecting. The requirements stated that we need the server name (which was provided in the parameters), the installed Windows version, amount of physical RAM, and processor count.

With the hash table created and the computer name populated, I begin by testing if the server is online. The native Powershell command Test-Connection is used similar to the ping.exe command. It is important to use the -Quiet parameter so that the output from this command does not go to the console. All we need is a true or false on the connection result. If the server is online, the Test-Connection will evaluate to true.

Once I have determined the server is online, I new up some variables. One might ask why do you create these variables when they are created as you assign a value to them? The reason is scope. The $computersystem, $os, and $processor variable are not assigned until a little farther down in the if…then statement. Had I let them be assigned as a normal course of the script in the if..then block, then the values of those variables would be lost when the if..then block completed. A variable is generally only known to the part of the script that instantiates it, and any code blocks inside that block. There are exceptions to this rule because you can explicitly set the scope of a variable when you create it. However, you should have a really, really good reason for creating a variable with a larger scope than what it initially has.

The if…then block is interesting. I test for a PSCredential object being passed in and also that the server name is not the name of the local computer. WMI has a little quirk (my term) that you cannot pass a credential parameter to it for a local connection; it just errors out. So to mitigate that error, I check to see if the script is connecting to the local computer. The only difference between the two Get-WmiObject blocks is that one passes the $credential parameter to the Get-WmiObject cmdlets and the other does not. If for some reason there is an access denied error on this block, the script simply assigns a message to the Windows Version property that indicates the error and moves on. Doing this allows the script 1) to alert the user that something was wrong with a particular machine; 2) to be filtered out with a Where-Object cmdlet downstream; and 3) to keep on pumping out data.

OK, so finally, we have some data to report. I am using the set_Item() method of the hash table. I access the properties I need and assign them to the specific hash table key that requires it. In testing, I did find that the “value” argument (the second argument to the set_Item() method) needs to be in quotes. So to make that work, I created a sub-expression [ $($object.property) ] to evaluate the property value out of the object and let the expandable string do its work.

Another point you might notice is that I do not format the data returned; specifically the physical RAM value, and it was a very conscious decision. The idea behind Powershell is to slice and dice data as much or as little as you want. For me to push the data out in a specific unit (GB, MB, etc) would be to force the user to my will. By outputting unformatted numbers, the user can use Select-object cmdlet to process that number with a calculated property. Likewise, with the computer name property, I am outputting the data exactly as it is returned to the script. If the user wants all of the computer names to be in all caps or all lowercase, they can again, use a calculated property in Select-Object to change the format of the output. The bottom line is that unless your script is specifically designed to format something, you should output only objects in the most raw form you are able to (or that makes sense). For one thing, it usually saves you some work, but it also makes it easier for the user to get the data in the form they want or need. If you would like some additional information on calculated properties, you can see a previous article I wrote here.

This brings us to the final part of the script. Using the hash table I created, with whatever values may have been pushed into it, I create the new PSCustomObject passing in that hash table as the properties to use. Once it is created, it is sent to the pipeline by using the Write-Output cmdlet.

There is an END block, as I mentioned, and I included that for form as a visual clue to whomever might be reading this that this is a script that can process pipeline input. It is not necessary to put the END block since there is no code, but to me, it looks incomplete without it. Whenever I see a script with BEGIN/PROCESS/END, I automatically assume we are dealing with pipeline input.

The purpose of the Scripting Games is to help people learn Powershell using real world scenarios and get live feedback from a diverse group of people. This is just one way to solve this problem. You may have another way or other ideas of how my idea could be implemented. If you want to see a truly advanced way to handle this, see this blog article, “Event 2: My way…” by Bartek Bielawski. After seeing his take on this event, I was considerably humbled about my efforts. I certainly learned a lot more than I expected by reading his solution…some things I didn’t even know you could do in Powershell to date.

Anyway, I hope you find the explanation useful. The comment section is open for discussion on my solution. Tell me what you did like or what you didn’t like. As a wise person I spoke to once said, “This point of this is to generate meaningful discussion.” So whether you discuss here or on powershell.org, let’s talk. See you in the next event!

Related Links:

Posted in Advanced Function, PSObjects, Scripting Games 2013, WMI | Tagged , , , , , , , | Comments Off on Advanced Event 2 – 2013 Scripting Games

Advanced Event 1 – 2013 Powershell Scripting Games

The games have finally started!  If you have not registered to participate, you can still jump in, however, the first event submissions are closed and we are in the midst of voting.

This year, things are being run a little differently.  For one, the games are being run by Powershell.Org.  This is a community-owned and run site dedicated to promoting the education and adoption of Powershell. Continue reading

Posted in Advanced Function, Cmdlet Help, Scripting Games 2013 | Tagged , | 2 Comments

The Scripting Games are Coming, The Scripting Games are Coming, The Scripting Games are Coming!

I was not able to participate in the games last year, but I am very excited to try the new changes to the games this year, get involved, and maybe even learn some new things about Powershell. If you are interested in Powershell and want a great place to learn a lot, I suggest that you start looking into the information on the powershell.org site.

It seems that the re-vamp of the games holds some interesting twists that have not been available before.

Posted in Scripting, Scripting Games | Tagged , , , , , | Comments Off on The Scripting Games are Coming, The Scripting Games are Coming, The Scripting Games are Coming!

Content Export in Sharepoint 2007

Sometimes, looking at an object model and all the options available can get you into trouble.

This happened with me while trying to figure out how to export content from a Sharepoint publishing site.  I was hoping to download information from one of our production sites and move it to a local development environment so that I can run multiple tests for a bug that I was researching.  However, I learned a few things about the Content Deployment namespace in Sharepoint. Continue reading

Posted in Scripting, Sharepoint 2007 | Tagged , , , , , , , , | 1 Comment

The Xbox Connection – Part 3

Thanks for joining me for this third and final article on using Powershell to get Xbox Live information.  In the first article, I introduced you to the Xbox web service API and showed how to get information on a user profile from the Xbox system.  In the second article, I showed how to get information on friends and games on your profile.  Now in this final article, I will show you how to get information on individual game achievements. Continue reading

Posted in Xbox 360, XML | Tagged , , , , | 1 Comment