Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET
PowerShell

PowerShell Problem Solver: Create Numbered Output Lists with PowerShell

I came across an interesting question the other day in a PowerShell forum. The poster wanted to include numbers for each line of output, which might look like this:

1.	Stopped  AdobeFlashPlaye... Adobe Flash Player Update Service
2.	Stopped  AeLookupSvc        Application Experience
3.	Stopped  ALG                Application Layer Gateway Service

I wasn’t sure at first why you would want to do this, as this feels like text parsing and not taking advantage of PowerShell’s object nature. But I started playing along. Here’s one approach.

get-service | foreach -Begin {$i=0} -Process {
$i++
"{0:D2}. {1} [{2}]" -f $i,$_.Name,$_.Status
}

Which gives you this:

A numbered output list created with Windows PowerShell. (Image Credit: Jeff Hicks)
A numbered output list created with Windows PowerShell. (Image Credit: Jeff Hicks)

That’s not bad I suppose if all you want to do is look at the list or save it to a text file because all you have is text. Although, you could use it to build an interactive console menu.

get-service | where {$_.status -eq 'running'} | foreach -Begin {$i=0} -Process {
$i++
"{0}. {1}" -f $i,$_.Name
} -outvariable menu
$r = Read-Host "Select a service to restart by number"
Write-Host "Restarting $($menu[$r-1])" -ForegroundColor Green
Restart-Service $menu[$r-1].Split()[1] -PassThru -force

I’m saving the results to $menu, so that I can access them later in the example.

Sponsored Content

Say Goodbye to Traditional PC Lifecycle Management

Traditional IT tools, including Microsoft SCCM, Ghost Solution Suite, and KACE, often require considerable custom configurations by T3 technicians (an expensive and often elusive IT resource) to enable management of a hybrid onsite + remote workforce. In many cases, even with the best resources, organizations are finding that these on-premise tools simply cannot support remote endpoints consistently and reliably due to infrastructure limitations.

An interactive console in Windows PowerShell. (Image Credit: Jeff Hicks)
An interactive console in Windows PowerShell. (Image Credit: Jeff Hicks)

That’s sorta cool. But because this is PowerShell, we should simply add a property.

$global:i=0
get-service | Select @{Name="Item#";Expression={$global:i++;$global:i}},Name,Displayname,Status

I am referencing the variable in the global scope because otherwise $i would be new for each object and everything would have a value of 1 in the Expression scriptblock. If I were to pipe this to Get-Member, I would see a new property called Item#. Given that, here’s my revised menu code:

$global:i=0
get-service | where {$_.status -eq 'running'} |
Select @{Name="Item";Expression={$global:i++;$global:i}},
Name -OutVariable menu | format-table -AutoSize
$r = Read-Host "Select a service to restart by number"
$svc = $menu | where {$_.item -eq $r}
Write-Host "Restarting $($svc.name)" -ForegroundColor Green
Restart-Service $svc.name -PassThru –force

Our revised output. (Image Credit: Jeff Hicks)
Our revised output. (Image Credit: Jeff Hicks)

This is all fun and proof of concept stuff. There’s no error handling, and if you run it again without setting $global:i back to 0, then your numbering will be off.

Everything I’ve shown you thus far is an ad-hoc approach, although certainly it lends itself to scripting. Another option would be to add your own type and formatting extensions. Let’s look at the formatting options. To create a custom format, you’ll need to create a .ps1xml. One way to get started is to export the current settings. Since I’m working with service objects, I can use an expression like this:

Get-FormatData -TypeName System.ServiceProcess.ServiceController | Export-FormatData -Path c:\scripts\myservice.ps1xml -IncludeScriptBlock -force

You can also open up $pshome\DotNetTypes.format.ps1xml in the ISE or copy and paste the relevant section into a new file. Now, you can edit and create your own formatting file. Here’s what I came up with:

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
    <ViewDefinitions>
        <View>
        <Name>numbered</Name>
        <ViewSelectedBy>
         <TypeName>System.ServiceProcess.ServiceController</TypeName>
            </ViewSelectedBy>
            <TableControl>
                <TableHeaders>
                    <TableColumnHeader>
                        <Label>Item</Label>
                        <Width>5</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>8</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>18</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>38</Width>
                    </TableColumnHeader>
                </TableHeaders>
                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                        <TableColumnItem>
                                <Scriptblock>$global:i++; $global:i</Scriptblock>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Status</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Name</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>DisplayName</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                </TableRowEntries>
            </TableControl>
        </View>
        </ViewDefinitions>
</Configuration>

I changed the Name setting because I will use this to reference this view. What I’m creating is in addition to the normal formatting. Next, I’ve created a new heading for the item number.
<TableColumnHeader>
     <Label>Item</Label>
     <Width>5</Width>
</TableColumnHeader>

Naturally I need to provide a value.
<TableColumnItem>
    <Scriptblock>$global:i++; $global:i</Scriptblock>
</TableColumnItem>

There is a concession here you have to accept. There is no easy way in the XML file to initialize $global:I every time you format the results.

The first time everything will look fine, but unless you reset $i it will keep increasing every time you format the output with this custom view. Once you have it complete, you can load it into your session and use it.

update-formatdata -AppendPath C:\scripts\myservice.ps1xml
get-service | format-table -View numbered
The get-service cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
The get-service cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)

Because this is formatted output, it is pretty to look at or save to a text file, but that’s the limits of its usefulness. If you want the Item number to be a part of the object, then you’ll need a custom type extension. This is actually pretty easy since we are adding a single property.

Update-TypeData -TypeName System.ServiceProcess.ServiceController -MemberType ScriptProperty -MemberName Item -Value {$global:i++; $global:i} -force
get-service | select Item,name,displayname,status

However, there is the same caveat about $global:i.

Although if you were using this in a script or function you could handle initializing the variable and cleaning up at the end.

Finally, you could take all of this one more step and create your own custom object with its own type and formatting directives, but I’ll leave that fun exercise to you.

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (0)

Leave a Reply

Live Webinar: Active Directory Security: What Needs Immediate Priority!Live on Tuesday, October 12th at 1 PM ET

Attacks on Active Directory are at an all-time high. Companies that are not taking heed are being punished, both monetarily and with loss of production.

In this webinar, you will learn:

  • How to prioritize vulnerability management
  • What attackers are leveraging to breach organizations
  • Where Active Directory security needs immediate attention
  • Overall strategy to secure your environment and keep it secured

Sponsored by: