Toolmaking Labs and Answers
Toolmaking Labs and Answers
Toolmaking Labs and Answers
Chapter
Lab
Simple Scripts and Functions
Lab Time: 20
Lab A
WMI is a great management tool and one we think toolmakers often take advantage of. Using the
new CIM cmdlets, write a function to query a computer and find all services by a combination of
startup mode such as Auto or Manual and the current state, e.g. Running. The whole point of toolmaking is to remain flexible and re-usable without having to constantly edit the script . You should
already have a start based on the examples in this chapter.
Lab B
Pretty hard to read and follow, isnt it? Grab the file from the MoreLunches site, open it in the ISE
and reformat it to make it easier to read. Dont forget to verify it works.
Lab
Chapter
Scope
Lab Time: 15
This script is supposed to create some new PSDrivesbased on environmental variables like %APPDATA% and%USERPROFILE%\DOCUMENTS. However, after the script runs the drives dont
exist. Why? What changes would you make?
Function New-Drives {
Param()
New-PSDrive -Name AppData -PSProvider FileSystem -Root $env:Appdata
New-PSDrive -Name Temp -PSProvider FileSystem -Root $env:TEMP
$mydocs=Join-Path -Path $env:userprofile -ChildPath Documents
New-PSDrive -Name Docs -PSProvider FileSystem -Root $mydocs
}
New-Drives
DIR temp: | measure-object property length sum
Chapter 5: Scope
Lab
Chapter
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
In this lab, we arent going to have you write any actual scripts or functions. Instead, we want you
to think about the design aspect, something many people overlook. Lets say youve been asked
to develop the following PowerShell tools. Even though the tool will be running from PowerShell
3.0, you dont have to assume that any remote computer is running PowerShell 3.0. Assume at least
PowerShell v2.
Lab A
Design a command that will retrieve the following information from one or more remote computers, using the indicated WMI classes and properties:
Win32_ComputerSystem:
Workgroup
Model
Manufacturer
From Win32_BIOS
SerialNumber
From Win32_OperatingSystem
Version
ServicePackMajorVersion
Your functions output should also include each computers name.
Ensure that your functions design includes a way to log errors to a text file, allowing the user to
specify an error file name but defaulting to C:\Errors.txt. Also plan ahead to create a custom view
so that your function always outputs a table, using the following column headers:
ComputerName
Workgroup
Manufacturer
Lab B
Design a tool that will retrieve the WMI Win32_Volume class from one or more remote computers.
For each computer and volume, the function should output the computers name, the volume name
(such as C:\), and the volumes free space and size in GB (using no more than 2 decimal places).
Only include volumes that represent fixed hard drives do not include optical or network drives in
the output. Keep in mind that any given computer may have multiple hard disks; your functions
output should include one object for each disk.
Ensure that your functions design includes a way to log errors to a text file, allowing the user to
specify an error file name but defaulting to C:\Errors.txt. Also, plan to create a custom view in the
future so that your function always outputs a table, using the following column headers:
ComputerName
Drive
FreeSpace
Size
Lab C
Design a command that will retrieve all running services on one or more remote computers. This
command will offer the option to log the names of failed computers to a text file. It will produce
a list that includes each running services name and display name, along with information about
the process that represents each running service. That information will include the process name,
virtual memory size, peak page file usage, and thread count. However, peak page file usage and
thread count will not display by default.
For each tool, think about the following design questions:
What would be a good name for your tool.
What sort of information do you need for each tool? (These might be potential parameters)
How do you think the tool would be run from a command prompt or what type of data will
it write to the pipeline??
8
Lab
Chapter
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
Using your design notes from the previous chapter, start building your tools. You wont have to
address every single design point right now. Well revise and expand these functions a bit more in
the next few chapters. For this chapter your functions should complete without error, even if they
are only using temporary output.
Lab A
Using your notes from Lab A in Chapter 6, write an advanced function that accepts one or more
computer names. For each computer name, use CIM or WMI to query the specified information.
For now, keep each propertys name, using ServicePackMajorVersion, Version, SerialNumber, etc.
But go ahead and translate the value for AdminPasswordStatus to the appropriate text equivalent.
Test the function by adding <function-name> -computerName localhost to the bottom of your script,
and then running the script. The output for a single computer should look something like this:
Workgroup
Manufacturer
Computername
Version
Model
AdminPassword
ServicePackMajorVersion
BIOSSerial
:
:
:
:
:
:
:
:
innotek GmbH
CLIENT2
6.1.7601
VirtualBox
NA
1
0
Lab B
Using your notes for Lab B from Chapter 6, write an advanced function that accepts one or more
computer names. For each computer name, use CIM or WMI to query the specified information.
Format the Size and FreeSpace property values in GB to 2 decimal points. Test the function by
adding <function-name> -computerName localhost to the bottom of your script, and then running
the script. The output for a single service should look something like this:
FreeSpace
Drive
--------- ----
0.07
\\?\Volume{8130d5f3...
9.78
C:\Temp\
2.72
C:\
2.72
D:\
Computername
-----------CLIENT2
CLIENT2
CLIENT2
CLIENT2
Size
---0.10
10.00
19.90
4.00
Lab C
Using your notes for Lab C from Chapter 6, write an advanced function that accepts one or more
computer names. For each computer name, use CIM or WMI to query all instances of Win32_Service where the State property is Running. For each service, get the ProcessID property. Then
query the matching instance of the Win32_Process class that is, the instance with the same ProcessID. Write a custom object to the pipeline that includes the service name and display name, the
computer name, and the process name, ID, virtual size, peak page file usage, and thread count. Test
the function by adding <function-name> -computerName localhost to the end of the script.
The output for a single service should look something like this:
Computername
ThreadCount
ProcessName
Name
VMSize
PeakPageFile
Displayname
10
:
:
:
:
:
:
:
CLIENT2
52
svchost.exe
wuauserv
499138560
247680
Windows Update
Standalone Lab
If time is limited, you can skip the 3 labs above and work on this single, stand-alone lab. Write
an advanced function named Get-SystemInfo. This function should accept one or more computer
names via a ComputerName parameter. It should then use WMI or CIM to query the Win32_
OperatingSystem class and Win32_ComputerSystem class for each computer. For each computer
queried, display the last boot time (in a standard date/time format), the computer name, and operating system version (all from Win32_OperatingSystem). Also, display the manufacturer and model
(from Win32_ComputerSystem). You should end up with a single object with all of this information for each computer.
NOTE: The last boot time property does not contain a human-readable date/time value; you will need to use the
class ConvertToDateTime() method to convert that value to a normal-looking date/time. Test the function by
adding Get-SystemInfo -computerName localhost to the end of the script.
:
:
:
:
:
VirtualBox
localhost
innotek GmbH
6/19/2012 8:55:34 AM
6.1.7601
11
12
Lab
Chapter
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
In this chapter were going to build on the functions you created in the last chapter using the concepts you hopefully picked up today. As you work through these labs, add verbose messages to
display key steps or progress information.
Lab A
Modify your advanced function from Chapter 7 Lab A to accept pipeline input for the
ComputerName parameter. Also, add verbose input that will display the name of each computer
contacted. Include code to verify that that the ComputerName parameter will not accept a null or
empty value. Test the function by adding
localhost | <function-name> -verbose to the end of your script.
Starting Get-Computerdata
Getting data from localhost
Win32_Computersystem
Win32_Bios
Win32_OperatingSystem
Workgroup
Manufacturer
Computername
Version
Model
AdminPassword
ServicePackMajorVersion
BIOSSerial
:
:
:
:
:
:
:
:
innotek GmbH
CLIENT2
6.1.7601
VirtualBox
NA
1
0
13
Lab B
Modify your advanced function from Chapter 7 Lab B to accept pipeline input for the ComputerName parameter. Add verbose output that will display the name of each computer contacted.
Ensure that the ComputerName parameter will not accept a null or empty value. Test the function
by adding localhost | <function-name> -verbose to the end of your script. The output should look
something like this:
VERBOSE: Starting Get-VolumeInfo
VERBOSE: Getting volume data from localhost
VERBOSE: Procssing volume \\?\Volume{8130d5f3-8e9b-11de-b460-806e6f6e6963}\
FreeSpace
Drive
Size
--------
--
---0.07
0.10
VERBOSE: Procssing volume C:\Temp\
9.78
10.00
VERBOSE: Procssing volume C:\
2.72
19.90
VERBOSE: Procssing volume D:\
2.72
4.00
VERBOSE: Ending Get-VolumeInfo
-----
Computername
---------
\\?\Volume{8130d5f3...
2
T
N
E
I
L
C
C:\Temp\
2
T
N
E
I
L
C
C:\
2TNEILC
D:\
2TNEILC
Lab C
Modify your advanced function from Lab C in Chapter 7 to accept pipeline input for the ComputerName parameter. Add verbose output that will display the name of each computer contacted, and
the name of each service queried. Ensure that the ComputerName parameter will not accept a null
or empty value. Test the function by running localhost | <function-name> -verbose. The output
for two services should look something like this:
VERBOSE: Starting Get-ServiceInfo
VERBOSE: Getting services from localhost
VERBOSE: Processing service AudioEndpointBuilder
Computername
ThreadCount
ProcessName
Name
VMSize
PeakPageFile
Displayname
14
:
:
:
:
:
:
:
CLIENT2
13
svchost.exe
AudioEndpointBuilder
172224512
83112
Windows Audio Endpoint Builder
Standalone Lab
Modify this function to accept pipeline input for the ComputerName parameter. Add verbose
output that will display the name of each computer contacted. Ensure that the ComputerName
parameter will not accept a null or empty value. Test the script by adding this line to the end of the
script file:
localhost,localhost | Get-SystemInfo -verbose
Model
ComputerName
Manufacturer
LastBootTime
OSVersion
:
:
:
:
:
VirtualBox
localhost
innotek GmbH
6/19/2012 8:55:34 AM
6.1.760
15
16
Chapter
Lab
Writing Help
Lab Time: 20
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
These labs will build on what youve already created, applying new concepts from this chapter.
Lab A
Add comment-based help to your advanced function from Lab A in Chapter 8. Include at least a
synopsis, description, and help for the ComputerName parameter. Test your help by adding help
<function-name> to the end of your script.
Lab B
Add comment-based help to your advanced function from Lab B in Chapter 8. Include at least a
synopsis, description, and help for the ComputerName parameter. Test your help by adding help
<function-name> to the end of your script.
Lab C
Add comment-based help to your advanced function from Lab C in Chapter 8. Include at least a
synopsis, description, and help for the ComputerName parameter. Test your help by adding help
<function-name> to the end of your script.
17
Standalone Lab
18
10
Lab
Chapter
Error Handling
Lab Time: 60
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
You are going to continue with the functions youve been building the last few chapters. The next
step is to begin incorporating some error handling using Try/Catch/Finally. If you havent done so,
take a few minutes to read the help content on Try/Catch/Finally. For any changes you make, dont
forget to update your comment-based help.
Lab A
Using Lab A from Chapter 9, add a ErrorLog parameter to your advanced function, which accepts
a filename for an error log and defaults to C:\Errors.txt. When the function is run with this parameter, failed computer names should be appended to the error log file.
Next, if the first WMI query fails, the function should output nothing for that computer and should
not attempt a second or third WMI query. Write an error to the pipeline containing each failed
computer name.
Test all of this by adding this line <function-name> -ComputerName localhost,NOTONLINE verbose to the end of your script. A portion of the output should look something like this:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
Starting Get-Computerdata
Getting data from localhost
Win32_Computersystem
Win32_Bios
Win32_OperatingSystem
Workgroup
:
Manufacturer
:
Computername
:
Version
SerialNumber
:
Model
AdminPassword
:
ServicePackMajorVersion :
innotek GmbH
CLIENT2
: 6.1.7601
0
: VirtualBox
NA
1
19
Lab B
Using Lab B from Chapter 9, add a ErrorLog parameter to your advanced function, which accepts
a filename for an error log and defaults to C:\Errors.txt. When the function is run with this parameter, failed computer names should be appended to the error log file.
Test all of this by adding this line <function-name> -ComputerName localhost,NOTONLINE verbose
to the end of your script. A portion of the output should look something like this:
VERBOSE: Starting Get-VolumeInfo
VERBOSE: Getting data from localhost
FreeSpace
--------
0.07
9.78
2.72
2.72
Drive
Computername
----
------------
\\?\Volume{8130d5f3... CLIENT2
C:\Temp\
CLIENT2
C:\
CLIENT2
D:\
CLIENT2
Size
---0.10
10.00
19.90
4.00
20
Lab C
Using Lab C from Chapter 9, add a LogErrors switch parameter to your advanced function. Also
add a ErrorFile parameter, which accepts a filename for an error log and defaults to C:\Errors.
txt. When the function is run with the -LogErrors parameter, failed computer names should be appended to the error log file. .Also, if LogErrors is used, the log file should be deleted at the start
of the function if it exists, so that each time the command starts with a fresh log file.
Test all of this by adding this line <function-name> -ComputerName localhost,NOTONLINE verbose logerrors to the end of your script. A portion of the output should look something like this:
VERBOSE: Processing service wuauserv
VERBOSE: Getting process for wuauserv
Computername : CLIENT2
ThreadCount : 45
ProcessName : svchost.exe
Name
: wuauserv
VMSize
: 499363840
PeakPageFile : 247680
Displayname : Windows Update
VERBOSE: Getting services from NOTOnline
Get-ServiceInfo : Failed to get service data from NOTOnline. The RPC server is
unavailable. (Exception from HRESULT: 0x800706BA)
At S:\Toolmaking\Ch10-LabC.ps1:109 char:39
+ localhost,NOTOnline,localhost | Get-ServiceInfo -logerrors -verbose
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo
: NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.
WriteErrorException,Get-ServiceInfo
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
21
Standalone Lab
22
Add a LogErrors switch to this advanced function. When the function is run with this switch,
failed computer names should be logged to C:\Errors.txt. This file should be deleted at the start of
the function each time it is run, so that it starts out fresh each time. If the first WMI query fails,
the function should output nothing for that computer and should not attempt a second WMI query.
Write an error to the pipeline containing each failed computer name.
Test your script by adding this line to the end of your script.
Get-SystemInfo -computername localhost,NOTONLINE,localhost -logerrors
A portion of the output should look something like this:
Model
: VirtualBox
ComputerName : localhost
Manufacturer : innotek GmbH
LastBootTime : 6/19/2012 8:55:34 AM
OSVersion
: 6.1.7601
Get-SystemInfo : NOTONLINE failed
At S:\Toolmaking\Ch10-Standalone.ps1:51 char:1
+ Get-SystemInfo -computername localhost,NOTONLINE,localhost -logerrors
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo
: NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.
WriteErrorException,Get-Syst
emInfo
Model
ComputerName
Manufacturer
LastBootTime
OSVersion
:
:
:
:
:
VirtualBox
localhost
innotek GmbH
6/19/2012 8:55:34 AM
6.1.7601
23
24
11
Chapter
Lab
Debugging Techniques
Lab Time: 45
Were sure youll have plenty of practice debugging your own scripts. But we want to reinforce
some of the concepts from this chapter and get you used to following a procedure. Never try to
debug a script simply by staring at it, hoping the error will jump out at you. It might, but more than
likely it may not be the only one. Follow our guidelines to identify bugs. Fix one thing at a time. If
it doesnt resolve the problem, change it back and repeat the process.
The functions listed here are broken and buggy. Weve numbered each line for reference purposes;
the numbers are not part of the actual function. How would you debug them? Revise them into
working solutions. Remember, you will need to dot source the script each time you make a change.
We recommend testing in the regular PowerShell console.
The function in Listing 11.8 is supposed to display some properties of running services sorted by
the service account.
The function in listing 11.9 is a bit more involved. Its designed to get recent event log entries for
a specified log on a specified computer. Events are sorted by the event source and added to a log
file. The filename is based on the date, computer name, and event source. At the end, the function
displays a directory listing of the logs. Hint: Clean up the formatting first.
Lab A
Function Get-ServiceInfo {
[cmdletbinding()]
Param([string]$Computername)
$services=Get-WmiObject -Class Win32_Services -filter state=Running `
-computername $computernam
Write-Host Found ($services.count) on $computername Foreground Green
$sevices | sort -Property startname,name Select -property `
startname,name,startmode,computername
}
25
Lab B
26
Function Export-EventLogSource {
[cmdletbinding()]
Param (
[Parameter(Position=0,Mandatory=$True,Helpmessage=Enter a computername,Va
lueFromPipeline=$True)]
[string]$Computername,
[Parameter(Position=1,Mandatory=$True,Helpmessage=Enter a classic event
log name like System)]
[string]$Log,
[int]$Newest=100
)
Begin {
Write-Verbose Starting export event source function
#the date format is case-sensitive
$datestring=Get-Date -Format yyyyMMdd
$logpath=Join-path -Path C:\Work -ChildPath $datestring
if (! (Test-Path -path $logpath) {
Write-Verbose Creating $logpath
mkdir $logpath
}
Write-Verbose Logging results to $logpath
}
Process {
Write-Verbose Getting newest $newest $log event log entries from $computername
Try {
Write-Host $computername.ToUpper -ForegroundColor Green
$logs=Get-EventLog -LogName $log -Newest $Newest -Computer $Computer -ErrorAction Stop
if ($logs) {
Write-Verbose Sorting $($logs.count) entries
$log | sort Source | foreach {
$logfile=Join-Path -Path $logpath -ChildPath $computername-$($_.Source).txt
$_ | Format-List TimeWritten,MachineName,EventID,EntryType,Message |
Out-File -FilePath $logfile -append
#clear variables for next time
Remove-Variable -Name logs,logfile
}
else {Write-Warning No logged events found for $log on $Computername}
}
Catch { Write-Warning $_.Exception.Message }
}
41
42
43
44
27
28
12
Lab
Chapter
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
We bet you can guess what is coming. Youll be adding type information and creating custom format files for the functions youve been working on the last several chapters. Use the dotnettypes.
format.ps1xml and other .ps1xml files as sources for sample layout. Copy and paste the XML into
your new format file. Dont forget that tags are case-sensitive.
Lab A
Modify your advanced function from Lab A in Chapter 10 so that the output object has
the type name MOL.ComputerSystemInfo. Then, create a custom view in a file named
C:\CustomViewA.format.ps1xml. The custom view should display objects of the type MOL.ComputerSystemInfo in a list format, displaying the information in a list as indicated in your design for
this lab. Go back to Chapter 6 to check what the output names should be.
At the bottom of the script file, add these commands to test:
Update-FormatData prepend c:\CustomViewA.format.ps1xml
<function-name> -ComputerName localhost
:
:
:
:
:
:
:
:
CLIENT2
NA
VirtualBox
innotek GmbH
0
6.1.7601
1
Note that the list labels are not exactly the same as the custom objects property names.
29
Lab B
Modify your advanced function Lab B from Chapter 10 so that the output object has the type name
MOL.DiskInfo. Then, create a custom view in a file named C:\CustomViewB.format.ps1xml. The
custom view should display objects of the type MOL.DiskInfo in a table format, displaying the information in a table as indicated in your design for this lab. Refer back to Chapter 6 for a refresher.
The column headers for the FreeSpace and Size properties should display FreeSpace(GB) and
Size(GB), respectively.
At the bottom of the script file, add these commands to test:
Update-FormatData prepend c:\CustomViewB.format.ps1xml
<function-name> -ComputerName localhost
Drive
----\\?\Volume{8130d5f3-8e9b-...
C:\Temp\
C:\
D:\
FreeSpace(GB)
------------0.07
9.78
2.72
2.72
Size(GB)
-------0.10
10.00
19.90
4.00
Note that the column headers are not exactly the same as the custom objects property names.
Lab C
Modify your advanced function Lab C from Chapter 10 so that the output object has the type name
MOL.ServiceProcessInfo. Then, create a custom view in a file named C:\CustomViewC.format.
ps1xml. The custom view should display objects of the type MOL.ServiceProcessInfo in a table format, displaying computername, service name, display name, process name, and process virtual size.
In addition to the table format, create a list view in the same file that displays the properties in
this order:
Computername
ProcessName
VMSize
ThreadCount
PeakPageFile
30
The final output should look something like this for the table.
ComputerName
-----------CLIENT2
CLIENT2
CLIENT2
CLIENT2
Service
------AudioEndpo...
BFE
BITS
Browser
Displayname
ProcessName
VM
---------- ---------- -Windows Audio E... svchost.exe
172208128
Base Filtering ... svchost.exe
69496832
ackground Inte... svchost.exe
499310592
Computer Browser
svchost.exe
499310592
:
:
:
:
:
:
:
CLIENT2
AudioEndpointBuilder
Windows Audio Endpoint Builder
svchost.exe
172208128
13
83112
Note that per the design specifications from Chapter 6 not every object property is displayed by
default and that some column headings are different than the actual property names.
31
32
13
Lab
Chapter
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
In this chapter you are going to assemble a module called PSHTools, from the functions and custom
views that youve been working on for the last several chapters. Create a folder in the user module
directory, called PSHTools. Put all of the files you will be creating in the labs into this folder.
Lab A
Create a single ps1xml file that contains all of the view definitions from the 3 existing format
files. Call the file PSHTools.format.ps1xml. Youll need to be careful. Each view is defined by the
<View></View> tags. These tags, and everything in between should go between the <ViewDefinition></ViewDefinition> tags.
Lab B
Create a single module file that contains the functions from the Labs A, B and C in Chapter 12,
which should be the most current version. Export all functions in the module. Be careful to copy the
function only. In your module file, also define aliases for your functions and export them as well.
Lab C
Create a module manifest for the PSHTools that loads the module and custom format files. Test the
module following these steps:
1.
3.
2.
4.
5.
6.
33
34
16
Chapter
Lab
Making Tools that Make Changes
Lab Time: 30
ShutDown 1
Restart 2
PowerOff 8
If the Force parameter is specified, add 4 to those values. So, if the command was Set-ComputerState computername localhost Action LogOff Force, then the value would be 4 (zero for
LogOff, plus 4 for Force). The execution of Win32Shutdown is what should be wrapped in the
implementing If block for WhatIf and Confirm support.
35
36
17
Lab
Chapter
Revisit the advanced function that you wrote for Lab A in Chapters 6 through 14 of this book.
Create a custom type extension for the object output by that function. Your type extension should
be a ScriptMethod named CanPing(), as outlined in this chapter. Save the type extension file as
PSHTools.ps1xml. Modify the PSHTools module manifest to load PSHTools.ps1xml, and then test
your revised module to make sure the CanPing() method works.
Here is a sample ps1xml file:
<?xml version=1.0 encoding=utf-8 ?>
<Types>
<Type>
<Name>MOL.ComputerSystemInfo</Name>
<Members>
<ScriptMethod>
<Name>CanPing</Name>
<Script>
Test-Connection -ComputerName $this.ComputerName -Quiet
</Script>
</ScriptMethod>
</Members>
</Type>
</Types>
37
38
19
Lab
Chapter
Create a text file named C:\Computers.csv. In it, place the following content:
ComputerName
LOCALHOST
NOTONLINE
Be sure there are no extra blank lines at the end of the file. Then, consider the following command:
Import-CSV C:\Computers.txt | Invoke-Command Script { Get-Service }
The help file for Invoke-Command indicates that its ComputerName parameter accepts pipeline
input ByValue. Therefore, our expectation is that the computer names in the CSV file will be fed
to the ComputerName parameter. But if you run the command, that isnt what happens. Troubleshoot this command using the techniques described in this chapter, and determine where the computer names from the CSV file are being bound.
39
40
20
Chapter
Lab
Create a new function in your existing PSHTools module. Name the new function Get-ComputerVolumeInfo. This functions output will include some information that your other functions already
produce, but this particular function is going to combine them all into a single, hierarchical object.
This function should accept one or more computer names on a ComputerName parameter. Dont
worry about error handling at this time. The output of this function should be a custom object with
the following properties:
ComputerName
OSVersion (Version from Win32_OperatingSystem)
SPVersion (ServicePackMajorVersion from Win32_OperatingSystem)
LocalDisks (all instances of Win32_LogicalDisk having a DriveType of 3)
Services (all instances of Win32_Service)
Processes (all instances of Win32_ProcessS)
The function will therefore be making at least four WMI queries to each specified computer.
41
42
22
Chapter
Lab
The .NET Framework contains a class named Dns, which lives within the System.Net namespace.
Read its documentation at http://msdn.microsoft.com/en-us/library/system.net.dns. Pay special attention to the static GetHostEntry() method. Use this method to return the IP address of www.
MoreLunches.com.
43
44
23
Lab
Chapter
In this lab youre going to start a project that youll work with over the next few chapters, so youll
want to make sure you have a working solution before moving on. Developing a graphical PowerShell script is always easier if you have a working command-line script. Weve already done that
part for you in the following listing.
LISTING CH23-LABFUNCTION
You can either retype or download the script from MoreLunches.com.
The function takes a computer name as a parameter and gets services via WMI based on usersupplied filter criteria. The function writes a subset of data to the pipeline. From the command line
it might be used like this:
Get-servicedata $env:computername -filter running | Out-GridView
Your task in this lab is to create the graphical form using PowerShell Studio. You should end up
with something like the form shown in figure 23.7.
Make the Running radio button checked by default. Youll find it easier later if you put the radio
buttons in a GroupBox control, plus it looks cooler. The script youre creating doesnt have to do
anything for this lab except display this form.
ANSWER - see code listing from MoreLunches.com chapter 23.
45
46
24
Lab
Chapter
In this lab youre going to continue where you left off in chapter 23. If you didnt finish, please do
so first or download the sample solution from MoreLunches.com. Now you need to wire up your
form and put some actions behind the controls.
First, set the Computername text box so that it defaults to the actual local computer name. Dont
use localhost.
TIP Look for the forms Load event function.
Then, connect the OK button so that it runs the Get-ServiceData function from the lab in chapter 23
and pipes the results to the pipeline. You can modify the function if you want. Use the form controls
to pass parameters to the function.
TIP You can avoid errors if you set the default behavior to search for running services.
You can test your form by sending output to Out-String and then Write-Host. For example, in your
form you could end up with a line like this:
<code to get data> | Out-String | Write-Host
In the next chapter youll learn better ways to handle form output.
ANSWER - see code listing from MoreLunches.com chapter 24
47
48
25
Lab
Chapter
Well keep things pretty simple for this lab. Using the PowerShell Studio lab project from chapter
24, add a RichTextBox control to display the results. Here are some things to remember:
Configure the control to use a fixed-width font like Consolas or Courier New.
The Text property must be a string, so explicitly format data as strings by usingOut-String.
Use the controls Clear() method to reset it or clear out any existing results.
If you need to move things around on your form, thats okay. You can download a sample solution
at MoreLunches.com.
ANSWER - see code listing from MoreLunches.com chapter 25
49
50
26
Lab
Chapter
Create a proxy function for the Export-CSV cmdlet. Name the proxy function Export-TDF. Remove the Delimiter parameter, and instead hardcode it to always use Delimiter `t (thats a
backtick, followed by the letter t, in double quotation marks).
Work with the proxy function in a script file. At the bottom of the file, after the closing } of the
function, put the following to test the function:
Get-Service | Export-TDF c:\services.tdf
Run the script to test the function, and verify that it creates a tab-delimited file named c:\services.tdf.
ANSWER - see code listing from MoreLunches.com chapter 26
51
52
27
Chapter
Lab
Setting Up Constrained
Demoting Endpoints
Lab Time: 30
Create a new, local user named TestMan on your computer. Be sure to assign a pass- word to the
account. Dont place the user in any user groups other than the default Users group.
Then, create a constrained endpoint on your computer. Name the endpoint ConstrainTest. Design
it to include only the SmbShare module and to make only the Get-SmbShare command visible
(in addition to a small core set of cmdlets like Exit-PSSession, Select-Object, and so forth). After
creating the session configura- tion, register the endpoint. Configure the endpoint to permit only
TestMan to con- nect (with Read and Execute permissions), and configure it to run all commands as
your local Administrator account. Be sure to provide the correct password for Administrator when
youre prompted.
Use Enter-PSSession to connect to the constrained endpoint. When doing so, use the Credential
parameter to specify the TestMan account, and provide the proper password when prompted. Ensure that you can run Get-SmbShare but not any other command (such as Get-SmbShareAccess).
53
54
Lab Answers
Learn PowerShell Toolmaking
in a Month of Lunches
Chapter
Lab Answers
Simple Scripts and Functions
Lab A
WMI is a great management tool and one we think toolmakers often take advantage of. Using the
new CIM cmdlets, write a function to query a computer and find all services by a combination of
startup mode such as Auto or Manual and the current state, e.g. Running. The whole point of toolmaking is to remain flexible and re-usable without having to constantly edit the script . You should
already have a start based on the examples in this chapter.
Lab B
Pretty hard to read and follow, isnt it? Grab the file from the MoreLunches site, open it in the
ISE and reformat it to make it easier to read. Dont forget to verify it works.
Answers
Part 1
Function Get-ServiceStartMode {
Param(
[string]$Computername=localhost,
[string]$StartMode=Auto,
[string]$State=Running
)
$filter=Startmode=$Startmode AND state=$State
Get-CimInstance -ClassName Win32_Service -ComputerName $Computername -Filter
$filter
}
#testing
Get-ServiceStartMode
Get-ServiceStartMode -Start Auto -State Stopped
Get-ServiceStartMode -StartMode Disabled -Computername SERVER01
Part 2
Function Get-DiskInfo {
Param (
[string]$computername=localhost,
[int]$MinimumFreePercent=10
)
$disks=Get-WmiObject -Class Win32_Logicaldisk -Filter Drivetype=3
foreach ($disk in $disks) {
$perFree=($disk.FreeSpace/$disk.Size)*100
if ($perFree -ge $MinimumFreePercent) {
$OK=$True
}
else {
$OK=$False
}
$disk | Select DeviceID,VolumeName,Size,FreeSpace,@
{Name=OK;Expression={$OK}}
} #close foreach
} #close function
Get-DiskInfo
Chapter
Lab Answers
Scope
This script is supposed to create some new PSDrivesbased on environmental variables like %APPDATA% and%USERPROFILE%\DOCUMENTS. However, after the script runs the drives dont
exist. Why? What changes would you make?
Function New-Drives {
Param()
New-PSDrive -Name AppData -PSProvider FileSystem -Root $env:Appdata
New-PSDrive -Name Temp -PSProvider FileSystem -Root $env:TEMP
$mydocs=Join-Path -Path $env:userprofile -ChildPath Documents
New-PSDrive -Name Docs -PSProvider FileSystem -Root $mydocs
}
New-Drives
DIR temp: | measure-object property length sum
Answer
The New-PSDrive cmdlet is creating the drive in the Function scope. Once the function ends the
drives disappear along with the scope.The solution is to use the Scope parameter with NewPSDrive. Using a value of Script will make them visible to the script so that the DIR command will
work. However, once the script ends the drives are still removed. If the intent was to make them
visible in the console, then the solution is to use a Scope value of Global.
Chapter 5: Scope
Chapter
Lab Answers
Tool Design Guidelines
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
In this lab, we arent going to have you write any actual scripts or functions. Instead, we want you
to think about the design aspect, something many people overlook. Lets say youve been asked
to develop the following PowerShell tools. Even though the tool will be running from PowerShell
3.0, you dont have to assume that any remote computer is running PowerShell 3.0. Assume at least
PowerShell v2.
Lab A
Design a command that will retrieve the following information from one or more remote computers, using the indicated WMI classes and properties:
Win32_ComputerSystem:
Workgroup
Model
Manufacturer
From Win32_BIOS
SerialNumber
From Win32_OperatingSystem
Version
ServicePackMajorVersion
Your functions output should also include each computers name.
Ensure that your functions design includes a way to log errors to a text file, allowing the user to
specify an error file name but defaulting to C:\Errors.txt. Also plan ahead to create a custom view
so that your function always outputs a table, using the following column headers:
ComputerName
Workgroup
Manufacturer
Again, you arent writing the script only outlining what you might do..
Lab B
Design a tool that will retrieve the WMI Win32_Volume class from one or more remote computers.
For each computer and volume, the function should output the computers name, the volume name
(such as C:\), and the volumes free space and size in GB (using no more than 2 decimal places).
Only include volumes that represent fixed hard drives do not include optical or network drives in
the output. Keep in mind that any given computer may have multiple hard disks; your functions
output should include one object for each disk.
Ensure that your functions design includes a way to log errors to a text file, allowing the user to
specify an error file name but defaulting to C:\Errors.txt. Also, plan to create a custom view in the
future so that your function always outputs a table, using the following column headers:
ComputerName
Drive
FreeSpace
Size
Lab C
Design a command that will retrieve all running services on one or more remote computers. This
command will offer the option to log the names of failed computers to a text file. It will produce
a list that includes each running services name and display name, along with information about
the process that represents each running service. That information will include the process name,
virtual memory size, peak page file usage, and thread count. However, peak page file usage and
thread count will not display by default.
For each tool, think about the following design questions:
What would be a good name for your tool.
What sort of information do you need for each tool? (These might be potential parameters)
How do you think the tool would be run from a command prompt or what type of data will it write to
the pipeline??
8
Answers
Lab A
Because we are getting information from a variety of WMI sources, a good function name might be
Get-ComputerData. Well need a string parameter for the name, a string for the log file and maybe a
switch parameter indicating that we want to log data. The function will need to make several WMI
queries and then it can write a custom object to the pipeline. We can get the computername from
one of the WMI classes. We could use the computername parameter, but by using something from
WMI well get the official computer name which is better if we test with something like localhost.
Since the AdminStatus property value an integer we can use a Switch statement to define a variable
with the interpretation as a string.
When creating a custom object, especially one where we need to make sure property names will match
the eventual custom view, a hash table will come in handy because we can use it with New-Object.
We can probably start out by having the function take computer names as parameters:
Get-Computerdata computername server01,server02
But eventually well want to be able to pipe computernames to it. Each computername should
produce a custom object.
Lab B
Since the command will get volume data information, a likely name would be Get-VolumeInfo or
Get-VolumeData. Like Lab A well need a string parameter for a computername, as well as a parameter for the eventlog and a switch to indicate whether or not to log errors. A sample command
might look like:
Get-VolumeInfo computername Server01 ErrorLog C:\work\errors.txt LogError
Also like Lab A, using a hash table with the new properties will make it easier to create and write
a custom object to the pipeline. Well also need to convert the size and free space by dividing the
size in bytes by 1GB. One way to handle the formatting requirement is to use the f operator.
$Size={0:N2} -f ($drive.capacity/1GB)
$Freespace={0:N2} -f ($drive.Freespace/1GB)
Lab C
This lab can follow the same outline as the first two in terms of computername, error log name and
whether or not to log files. Because we need to get the process id of each service, well need to use
WMI or CIM. The Get-Service cmdlet returns a service object, but it doesnt include the process
id. Once we have the service object we can execute another WMI query to get the process object.
It will most likely be easiest to create a hash table with all of the required properties from the 2
WMI classes. For now, well include all the properties. Later we can create a custom view with
only the desired, default properties.
Since this function is getting service information, a good name might be Get-ServiceInfo.
Chapter 6: Tool Design Guidelines
10
Chapter
Lab Answers
Advanced Functions, Part 1
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
Using your design notes from the previous chapter, start building your tools. You wont have to
address every single design point right now. Well revise and expand these functions a bit more in
the next few chapters. For this chapter your functions should complete without error, even if they
are only using temporary output.
Lab A
Using your notes from Lab A in Chapter 6, write an advanced function that accepts one or more
computer names. For each computer name, use CIM or WMI to query the specified information.
For now, keep each propertys name, using ServicePackMajorVersion, Version, SerialNumber, etc.
But go ahead and translate the value for AdminPasswordStatus to the appropriate text equivalent.
Test the function by adding <function-name> -computerName localhost to the bottom of your script,
and then running the script. The output for a single computer should look something like this:
Workgroup
Manufacturer
Computername
Version
Model
AdminPassword
ServicePackMajorVersion
BIOSSerial
:
:
:
:
:
:
:
:
innotek GmbH
CLIENT2
6.1.7601
VirtualBox
NA
1
0
11
Lab B
Using your notes for Lab B from Chapter 6, write an advanced function that accepts one or more
computer names. For each computer name, use CIM or WMI to query the specified information.
Format the Size and FreeSpace property values in GB to 2 decimal points. Test the function by
adding <function-name> -computerName localhost to the bottom of your script, and then running
the script. The output for a single service should look something like this:
FreeSpace
Drive
--------- ----
0.07
\\?\Volume{8130d5f3...
9.78
C:\Temp\
2.72
C:\
Computername
-----------CLIENT2
CLIENT2
CLIENT2
2.72
CLIENT2
D:\
Size
--- 0.10
10.00
19.90
4.00
13
Lab C
Using your notes for Lab C from Chapter 6, write an advanced function that accepts one or more
computer names. For each computer name, use CIM or WMI to query all instances of Win32_Service where the State property is Running. For each service, get the ProcessID property. Then
query the matching instance of the Win32_Process class that is, the instance with the same ProcessID. Write a custom object to the pipeline that includes the service name and display name, the
computer name, and the process name, ID, virtual size, peak page file usage, and thread count. Test
the function by adding <function-name> -computerName localhost to the end of the script.
The output for a single service should look something like this:
Computername
ThreadCount
ProcessName
Name
VMSize
PeakPageFile
Displayname
:
:
:
:
:
:
:
CLIENT2
52
svchost.exe
wuauserv
499138560
247680
Windows Update
Displayname=$service.DisplayName
}
#get the associated process
$process=Get-WMIObject -class Win32_Process -computername $Computer
-Filter ProcessID=$($service.processid)
$hash.Add(ProcessName,$process.name)
$hash.add(VMSize,$process.VirtualSize)
$hash.Add(PeakPageFile,$process.PeakPageFileUsage)
$hash.add(ThreadCount,$process.Threadcount)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach service
} #foreach computer
}
Get-ServiceInfo -ComputerName localhost
Standalone Lab
If time is limited, you can skip the 3 labs above and work on this single, stand-alone lab. Write
an advanced function named Get-SystemInfo. This function should accept one or more computer
names via a ComputerName parameter. It should then use WMI or CIM to query the Win32_
OperatingSystem class and Win32_ComputerSystem class for each computer. For each computer
queried, display the last boot time (in a standard date/time format), the computer name, and operating system version (all from Win32_OperatingSystem). Also, display the manufacturer and model
(from Win32_ComputerSystem). You should end up with a single object with all of this information for each computer.
NOTE: The last boot time property does not contain a human-readable date/time value; you will need to use the
class ConvertToDateTime() method to convert that value to a normal-looking date/time. Test the function by
adding Get-SystemInfo -computerName localhost to the end of the script.
:
:
:
:
:
VirtualBox
localhost
innotek GmbH
6/19/2012 8:55:34 AM
6.1.7601
15
16
Chapter
Lab Answers
Advanced Functions, Part 2
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
In this chapter were going to build on the functions you created in the last chapter using the concepts you hopefully picked up today. As you work through these labs, add verbose messages to
display key steps or progress information.
Lab A
Modify your advanced function from Chapter 7 Lab A to accept pipeline input for the ComputerName
parameter. Also, add verbose input that will display the name of each computer contacted. Include
code to verify that that the ComputerName parameter will not accept a null or empty value. Test
the function by adding
localhost | <function-name> -verbose to the end of your script.
Starting Get-Computerdata
Getting data from localhost
Win32_Computersystem
Win32_Bios
Win32_OperatingSystem
Workgroup
Manufacturer
Computername
Version
Model
AdminPassword
ServicePackMajorVersion
BIOSSerial
:
:
:
:
:
:
:
:
innotek GmbH
CLIENT2
6.1.7601
VirtualBox
NA
1
0
17
$hash.Add(Version,$os.Version)
$hash.Add(ServicePackMajorVersion,$os.ServicePackMajorVersion)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach
} #process
End {
Write-Verbose Ending Get-Computerdata
}
}
localhost | Get-Computerdata -verbose
Lab B
Modify your advanced function from Chapter 7 Lab B to accept pipeline input for the ComputerName parameter. Add verbose output that will display the name of each computer contacted.
Ensure that the ComputerName parameter will not accept a null or empty value. Test the function
by adding localhost | <function-name> -verbose to the end of your script. The output should look
something like this:
VERBOSE: Starting Get-VolumeInfo
VERBOSE: Getting volume data from localhost
VERBOSE: Procssing volume \\?\Volume{8130d5f3-8e9b-11de-b460-806e6f6e6963}\
FreeSpace Drive
--------- ----
0.07
\\?\Volume{8130d5f3...
VERBOSE: Procssing volume C:\Temp\
9.78
C:\Temp\
VERBOSE: Procssing volume C:\
2.72
C:\
VERBOSE: Procssing volume D:\
2.72
D:\
VERBOSE: Ending Get-VolumeInfo
Computername
------------
CLIENT2
Size
---0.10
CLIENT2
10.00
CLIENT2
CLIENT2
19.90
4.00
19
[ValidateNotNullorEmpty()]
[string[]]$ComputerName
)
Begin {
Write-Verbose Starting Get-VolumeInfo
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting volume data from $computer
$data = Get-WmiObject -Class Win32_Volume -computername $Computer -Filter
DriveType=3
Foreach ($drive in $data) {
Write-Verbose Procssing volume $($drive.name)
#format size and freespace
$Size={0:N2} -f ($drive.capacity/1GB)
$Freespace={0:N2} -f ($drive.Freespace/1GB)
#Define a hashtable to be used for property names and values
$hash=@{
Computername=$drive.SystemName
Drive=$drive.Name
FreeSpace=$Freespace
Size=$Size
}
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach
#clear $data for next computer
Remove-Variable -Name data
} #foreach computer
} #Process
End {
Write-Verbose Ending Get-VolumeInfo
}
}
localhost | Get-VolumeInfo -verbose
20
Lab C
erName parameter. Add verbose output that will display the name of each computer contacted, and
the name of each service queried. Ensure that the ComputerName parameter will not accept a null
or empty value. Test the function by running localhost | <function-name> -verbose. The output
for two services should look something like this:
VERBOSE: Starting Get-ServiceInfo
VERBOSE: Getting services from localhost
VERBOSE: Processing service AudioEndpointBuilder
Computername
ThreadCount
ProcessName
Name
VMSize
PeakPageFile
Displayname
:
:
:
:
:
:
:
CLIENT2
13
svchost.exe
AudioEndpointBuilder
172224512
83112
Windows Audio Endpoint Builder
21
}
#get the associated process
$process=Get-WMIObject -class Win32_Process -computername $Computer
-Filter ProcessID=$($service.processid)
$hash.Add(ProcessName,$process.name)
$hash.add(VMSize,$process.VirtualSize)
$hash.Add(PeakPageFile,$process.PeakPageFileUsage)
$hash.add(ThreadCount,$process.Threadcount)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach service
} #foreach computer
} #process
End {
Write-Verbose Ending Get-ServiceInfo
}
}
localhost | Get-ServiceInfo -verbose
Standalone Lab
22
Write-Output $obj
}
}
}
Modify this function to accept pipeline input for the ComputerName parameter. Add verbose
output that will display the name of each computer contacted. Ensure that the ComputerName
parameter will not accept a null or empty value. Test the script by adding this line to the end of the
script file:
localhost,localhost | Get-SystemInfo -verbose
Model
ComputerName
Manufacturer
LastBootTime
OSVersion
:
:
:
:
:
VirtualBox
localhost
innotek GmbH
6/19/2012 8:55:34 AM
6.1.760
23
24
Chapter
Lab Answers
Writing Help
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
These labs will build on what youve already created, applying new concepts from this chapter.
Lab A
Add comment-based help to your advanced function from Lab A in Chapter 8. Include at least a
synopsis, description, and help for the ComputerName parameter. Test your help by adding help
<function-name> to the end of your script.
Here is a possible solution:
Function Get-ComputerData {
<#
.SYNOPSIS
Get computer related data
.DESCRIPTION
This command will query a remote computer and return a custom object with system information pulled from WMI. Depending on the computer some information
may not be available.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
.EXAMPLE
PS C:\> Get-ComputerData Server01
Run the command and query Server01.
.EXAMPLE
25
26
$hash.Add(SerialNumber,$bios.SerialNumber)
Write-Verbose Win32_OperatingSystem
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName
$Computer
$hash.Add(Version,$os.Version)
$hash.Add(ServicePackMajorVersion,$os.ServicePackMajorVersion)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach
} #process
End {
Write-Verbose Ending Get-Computerdata
}
}
help Get-Computerdata -full
Lab B
Add comment-based help to your advanced function from Lab B in Chapter 8. Include at least a
synopsis, description, and help for the ComputerName parameter. Test your help by adding help
<function-name> to the end of your script.
Here is a possible solution:
Function Get-VolumeInfo {
<#
.SYNOPSIS
Get information about fixed volumes
.DESCRIPTION
This command will query a remote computer and return information about fixed volumes. The function will ignore network, optical and other removable drives.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
.EXAMPLE
PS C:\> Get-VolumeInfo Server01
27
28
Lab C
Add comment-based help to your advanced function from Lab C in Chapter 8. Include at least a
synopsis, description, and help for the ComputerName parameter. Test your help by adding help
<function-name> to the end of your script.
Here is a possible solution:
Function Get-ServiceInfo {
<#
.SYNOPSIS
Get service information
.DESCRIPTION
This command will query a remote computer for running services and
write a custom object to the pipeline that includes service details
as well as a few key properties from the associated process. You
must run this command with credentials that have admin rights on
any remote computers.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
.EXAMPLE
PS C:\> Get-ServiceInfo Server01
Run the command and query Server01.
.EXAMPLE
PS C:\> get-content c:\work\computers.txt | Get-ServiceInfo
29
This expression will go through a list of computernames and pipe each name to
the command.
#>
[cmdletbinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$ComputerName
)
Begin {
Write-Verbose Starting Get-ServiceInfo
}
Process {
foreach ($computer in $computerName) {
$data = Get-WmiObject -Class Win32_Service -computername $Computer -Filter
State=Running
foreach ($service in $data) {
$hash=@{
Computername=$data[0].Systemname
Name=$service.name
Displayname=$service.DisplayName
}
#get the associated process
$process=Get-WMIObject -class Win32_Process -computername $Computer
-Filter ProcessID=$($service.processid)
$hash.Add(ProcessName,$process.name)
$hash.add(VMSize,$process.VirtualSize)
$hash.Add(PeakPageFile,$process.PeakPageFileUsage)
$hash.add(ThreadCount,$process.Threadcount)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach service
} #foreach computer
} #process
End {
Write-Verbose Ending Get-ServiceInfo
}
}
help Get-ServiceInfo -full
30
Standalone Lab
31
32
10
Chapter
Lab Answers
Error Handling
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
You are going to continue with the functions youve been building the last few chapters. The next
step is to begin incorporating some error handling using Try/Catch/Finally. If you havent done so,
take a few minutes to read the help content on Try/Catch/Finally. For any changes you make, dont
forget to update your comment-based help.
Lab A
Using Lab A from Chapter 9, add a ErrorLog parameter to your advanced function, which accepts
a filename for an error log and defaults to C:\Errors.txt. When the function is run with this parameter, failed computer names should be appended to the error log file.
Next, if the first WMI query fails, the function should output nothing for that computer and should
not attempt a second or third WMI query. Write an error to the pipeline containing each failed
computer name.
Test all of this by adding this line <function-name> -ComputerName localhost,NOTONLINE verbose
to the end of your script. A portion of the output should look something like this:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
Starting Get-Computerdata
Getting data from localhost
Win32_Computersystem
Win32_Bios
Win32_OperatingSystem
Workgroup
:
Manufacturer
:
Computername
:
Version
SerialNumber
:
Model
AdminPassword
:
ServicePackMajorVersion :
innotek GmbH
CLIENT2
: 6.1.7601
0
: VirtualBox
NA
1
33
#>
[cmdletbinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$ComputerName,
[string]$ErrorLog=C:\Errors.txt
)
Begin {
Write-Verbose Starting Get-Computerdata
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting data from $computer
Try {
Write-Verbose Win32_Computersystem
$cs = Get-WmiObject -Class Win32_Computersystem -ComputerName $Computer -ErrorAction Stop
#decode the admin password status
Switch ($cs.AdminPasswordStatus) {
1 { $aps=Disabled }
2 { $aps=Enabled }
3 { $aps=NA }
4 { $aps=Unknown }
}
#Define a hashtable to be used for property names and values
$hash=@{
Computername=$cs.Name
Workgroup=$cs.WorkGroup
AdminPassword=$aps
Model=$cs.Model
Manufacturer=$cs.Manufacturer
}
} #Try
Catch {
35
} #Catch
#if there were no errors then $hash will exist and we can continue and
assume
#all other WMI queries will work without error
If ($hash) {
Write-Verbose Win32_Bios
$bios = Get-WmiObject -Class Win32_Bios -ComputerName $Computer
$hash.Add(SerialNumber,$bios.SerialNumber)
Write-Verbose Win32_OperatingSystem
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName
$Computer
$hash.Add(Version,$os.Version)
$hash.Add(ServicePackMajorVersion,$os.ServicePackMajorVersion)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
causes
36
Get-Computerdata
-verbose
Lab B
a filename for an error log and defaults to C:\Errors.txt. When the function is run with this parameter, failed computer names should be appended to the error log file.
Test all of this by adding this line <function-name> -ComputerName localhost,NOTONLINE verbose to the end of your script. A portion of the output should look something like this:
VERBOSE: Starting Get-VolumeInfo
VERBOSE: Getting data from localhost
FreeSpace
Drive
Computername
Size
--------
----
-----------
---0.07
\\?\Volume{8130d5f3...
CLIENT2
0.10
9.78
C:\Temp\
CLIENT2
10.00
2.72
C:\
CLIENT2
19.90
2.72
D:\
CLIENT2
4.00
VERBOSE: Getting data from NotOnline
Get-VolumeInfo : Failed to get volume information from NotOnline. The RPC server is
unavailable. (Exception from HRESULT: 0x800706BA)
At S:\Toolmaking\Ch10-LabB.ps1:96 char:27
+ localhost,NotOnline | Get-VolumeInfo -Verbose -logerrors
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo
: NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.
WriteErrorException,Get-VolumeInfo
VERBOSE: Logging errors to C:\Errors.txt
VERBOSE: Ending Get-VolumeInfo
37
38
Computername=$drive.SystemName
Drive=$drive.Name
FreeSpace=$Freespace
Size=$Size
}
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach
#clear $data for next computer
Remove-Variable -Name data
} #Try
Catch {
#create an error message
$msg=Failed to get volume information from $computer. $($_.Exception.Message)
Write-Error $msg
Write-Verbose Logging errors to $errorlog
$computer | Out-File -FilePath $Errorlog -append
}
} #foreach computer
} #Process
End {
Write-Verbose Ending Get-VolumeInfo
}
}
localhost,NotOnline | Get-VolumeInfo -Verbose
39
Lab C
Using Lab C from Chapter 9, add a LogErrors switch parameter to your advanced function. Also
add a ErrorFile parameter, which accepts a filename for an error log and defaults to C:\Errors.
txt. When the function is run with the -LogErrors parameter, failed computer names should be appended to the error log file. .Also, if LogErrors is used, the log file should be deleted at the start
of the function if it exists, so that each time the command starts with a fresh log file.
Test all of this by adding this line <function-name> -ComputerName localhost,NOTONLINE verbose
logerrors to the end of your script. A portion of the output should look something like this:
VERBOSE: Processing service wuauserv
VERBOSE: Getting process for wuauserv
Computername : CLIENT2
ThreadCount : 45
ProcessName : svchost.exe
Name
: wuauserv
VMSize
: 499363840
PeakPageFile : 247680
Displayname : Windows Update
VERBOSE: Getting services from NOTOnline
Get-ServiceInfo : Failed to get service data from NOTOnline. The RPC server is
unavailable. (Exception from HRESULT: 0x800706BA)
At S:\Toolmaking\Ch10-LabC.ps1:109 char:39
+ localhost,NOTOnline,localhost | Get-ServiceInfo -logerrors -verbose
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo
: NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.
WriteErrorException,Get-ServiceInfo
VERBOSE:
VERBOSE:
VERBOSE:
VERBOSE:
40
.DESCRIPTION
This command will query a remote computer for running services and write a custom object to the pipeline that includes service details as well as a few
key properties from the associated process. You must run this command with
credentials that have admin rights on any remote computers.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
.PARAMETER ErrorLog
Specify a path to a file to log errors. The default is C:\Errors.txt
.PARAMETER LogErrors
If specified, computer names that cant be accessed will be logged to the file
specified by -Errorlog.
.EXAMPLE
PS C:\> Get-ServiceInfo Server01
Run the command and query Server01.
.EXAMPLE
PS C:\> get-content c:\work\computers.txt | Get-ServiceInfo -logerrors
This expression will go through a list of computernames and pipe each name to
the command. Computernames that cant be accessed will be written to the log
file.
#>
[cmdletbinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$ComputerName,
[string]$ErrorLog=C:\Errors.txt,
[switch]$LogErrors
)
Begin {
Write-Verbose Starting Get-ServiceInfo
#if -LogErrors and error log exists, delete it.
if ( (Test-Path -path $errorLog) -AND $LogErrors) {
Write-Verbose Removing $errorlog
41
Remove-Item $errorlog
}
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting services from $computer
Try {
$data = Get-WmiObject -Class Win32_Service -computername $Computer
-Filter State=Running -ErrorAction Stop
foreach ($service in $data) {
Write-Verbose Processing service $($service.name)
$hash=@{
Computername=$data[0].Systemname
Name=$service.name
Displayname=$service.DisplayName
}
#get the associated process
Write-Verbose Getting process for $($service.name)
$process=Get-WMIObject -class Win32_Process -computername $Computer -Filter ProcessID=$($service.processid) -ErrorAction Stop
$hash.Add(ProcessName,$process.name)
$hash.add(VMSize,$process.VirtualSize)
$hash.Add(PeakPageFile,$process.PeakPageFileUsage)
$hash.add(ThreadCount,$process.Threadcount)
#create a custom object from the hash table
New-Object -TypeName PSObject -Property $hash
} #foreach service
}
Catch {
#create an error message
$msg=Failed to get service data from $computer. $($_.Exception.
Message)
Write-Error $msg
if ($LogErrors) {
Write-Verbose Logging errors to $errorlog
$computer | Out-File -FilePath $Errorlog -append
}
42
}
} #foreach computer
} #process
End {
Write-Verbose Ending Get-ServiceInfo
}
}
Get-ServiceInfo -ComputerName localhost,NOTOnline,localhost
-logerrors
Standalone Lab
43
puter
Add a LogErrors switch to this advanced function. When the function is run with this switch,
failed computer names should be logged to C:\Errors.txt. This file should be deleted at the start of
the function each time it is run, so that it starts out fresh each time. If the first WMI query fails,
the function should output nothing for that computer and should not attempt a second WMI query.
Write an error to the pipeline containing each failed computer name.
Test your script by adding this line to the end of your script.
Get-SystemInfo -computername localhost,NOTONLINE,localhost -logerrors
A portion of the output should look something like this:
Model
: VirtualBox
ComputerName : localhost
Manufacturer : innotek GmbH
LastBootTime : 6/19/2012 8:55:34 AM
OSVersion
: 6.1.7601
Get-SystemInfo : NOTONLINE failed
At S:\Toolmaking\Ch10-Standalone.ps1:51 char:1
+ Get-SystemInfo -computername localhost,NOTONLINE,localhost -logerrors
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo
: NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.
WriteErrorException,Get-Syst
emInfo
Model
ComputerName
Manufacturer
LastBootTime
OSVersion
44
:
:
:
:
:
VirtualBox
localhost
innotek GmbH
6/19/2012 8:55:34 AM
6.1.7601
45
OSVersion=$os.version
Manufacturer=$cs.manufacturer
Model=$cs.model
}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
}
}
}
Get-SystemInfo -computername localhost,NOTONLINE,localhost -logerrors
46
11
Chapter
Lab Answers
Debugging Techniques
Were sure youll have plenty of practice debugging your own scripts. But we want to reinforce
some of the concepts from this chapter and get you used to following a procedure. Never try to
debug a script simply by staring at it, hoping the error will jump out at you. It might, but more than
likely it may not be the only one. Follow our guidelines to identify bugs. Fix one thing at a time. If
it doesnt resolve the problem, change it back and repeat the process.
The functions listed here are broken and buggy. Weve numbered each line for reference purposes;
the numbers are not part of the actual function. How would you debug them? Revise them into
working solutions. Remember, you will need to dot source the script each time you make a change.
We recommend testing in the regular PowerShell console.
The function in Listing 11.8 is supposed to display some properties of running services sorted by
the service account.
The function in listing 11.9 is a bit more involved. Its designed to get recent event log entries for
a specified log on a specified computer. Events are sorted by the event source and added to a log
file. The filename is based on the date, computer name, and event source. At the end, the function
displays a directory listing of the logs. Hint: Clean up the formatting first.
Lab A
Function Get-ServiceInfo {
[cmdletbinding()]
Param([string]$Computername)
$services=Get-WmiObject -Class Win32_Services -filter state=Running `
-computername $computernam
Write-Host Found ($services.count) on $computername Foreground Green
$sevices | sort -Property startname,name Select -property `
startname,name,startmode,computername
}
47
Lab B
Function Export-EventLogSource {
[cmdletbinding()]
Param (
[Parameter(Position=0,Mandatory=$True,Helpmessage=Enter a computername,Va
lueFromPipeline=$True)]
[string]$Computername,
[Parameter(Position=1,Mandatory=$True,Helpmessage=Enter a classic event
log name like System)]
[string]$Log,
[int]$Newest=100
)
Begin {
Write-Verbose Starting export event source function
#the date format is case-sensitive
$datestring=Get-Date -Format yyyyMMdd
$logpath=Join-path -Path C:\Work -ChildPath $datestring
if (! (Test-Path -path $logpath) {
Write-Verbose Creating $logpath
mkdir $logpath
}
Write-Verbose Logging results to $logpath
}
Process {
Write-Verbose Getting newest $newest $log event log entries from $computername
Try {
Write-Host $computername.ToUpper -ForegroundColor Green
$logs=Get-EventLog -LogName $log -Newest $Newest -Computer $Computer -ErrorAction Stop
if ($logs) {
Write-Verbose Sorting $($logs.count) entries
$log | sort Source | foreach {
$logfile=Join-Path -Path $logpath -ChildPath $computername-$($_.Source).
txt
$_ | Format-List TimeWritten,MachineName,EventID,EntryType,Message |
Out-File -FilePath $logfile -append
#clear variables for next time
Remove-Variable -Name logs,logfile
}
else {Write-Warning No logged events found for $log on $Computername}
}
Catch { Write-Warning $_.Exception.Message }
40
41
42
43
44
}
End {dir $logpath
Write-Verbose Finished export event source function
}
}
Answer
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Function Export-EventLogSource {
[cmdletbinding()]
Param (
[Parameter(Position=0,Mandatory=$True,Helpmessage=Enter a computername,Va
lueFromPipeline=$True)]
[string]$Computername,
[Parameter(Position=1,Mandatory=$True,Helpmessage=Enter a classic event
log name like System)]
[string]$Log,
[int]$Newest=100
)
Begin {
Write-Verbose Starting export event source function
#the date format is case-sensitive
$datestring=Get-Date -Format yyyyMMdd
$logpath=Join-path -Path C:\Work -ChildPath $datestring
if (! (Test-Path -path $logpath) {
Write-Verbose Creating $logpath
mkdir $logpath
}
Write-Verbose Logging results to $logpath
}
Process {
Write-Verbose Getting newest $newest $log event log entries from $computername
Try {
49
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
50
name
}
else {
Write-Warning No logged events found for $log on $Computer}
}
Catch {
Write-Warning $_.Exception.Message
}
}
End {
dir $logpath
Write-Verbose Finished export event source function
}
}
12
Chapter
Lab Answers
Creating Custom Format Views
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
We bet you can guess what is coming. Youll be adding type information and creating custom format files for the functions youve been working on the last several chapters. Use the dotnettypes.
format.ps1xml and other .ps1xml files as sources for sample layout. Copy and paste the XML into
your new format file. Dont forget that tags are case-sensitive.
Lab A
Modify your advanced function from Lab A in Chapter 10 so that the output object has
the type name MOL.ComputerSystemInfo. Then, create a custom view in a file named
C:\CustomViewA.format.ps1xml. The custom view should display objects of the type MOL.
ComputerSystemInfo in a list format, displaying the information in a list as indicated in your design for this lab. Go back to Chapter 6 to check what the output names should be.
At the bottom of the script file, add these commands to test:
Update-FormatData prepend c:\CustomViewA.format.ps1xml
<function-name> -ComputerName localhost
AdminPassword
Model
Manufacturer
: CLIENT2
: NA
: VirtualBox
: innotek GmbH
BIOSSerialNumber
:0
OSVersion
: 6.1.7601
SPVersion
:1
Note that the list labels are not exactly the same as the custom objects property names.
Chapter 12: Creating Custom Format Views
51
52
</View>
</ViewDefinitions>
</Configuration>
Sample Script
Function Get-ComputerData {
<#
.SYNOPSIS
Get computer related data
.DESCRIPTION
This command will query a remote computer and return a custom object with system information pulled from WMI. Depending on the computer some information
may not be available.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
.PARAMETER ErrorLog
Specify a path to a file to log errors. The default is C:\Errors.txt
.EXAMPLE
PS C:\> Get-ComputerData Server01
Run the command and query Server01.
.EXAMPLE
PS C:\> get-content c:\work\computers.txt | Get-ComputerData -Errorlog c:\logs\
errors.txt
This expression will go through a list of computernames and pipe each name to
the command. Computernames that cant be accessed will be written to the log
file.
#>
[cmdletbinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$ComputerName,
[string]$ErrorLog=C:\Errors.txt
)
53
Begin {
Write-Verbose Starting Get-Computerdata
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting data from $computer
Try {
Write-Verbose Win32_Computersystem
$cs = Get-WmiObject -Class Win32_Computersystem -ComputerName $Computer -ErrorAction Stop
#decode the admin password status
Switch ($cs.AdminPasswordStatus) {
1 { $aps=Disabled }
2 { $aps=Enabled }
3 { $aps=NA }
4 { $aps=Unknown }
}
#Define a hashtable to be used for property names and values
$hash=@{
Computername=$cs.Name
Workgroup=$cs.WorkGroup
AdminPassword=$aps
Model=$cs.Model
Manufacturer=$cs.Manufacturer
}
} #Try
Catch {
#create an error message
$msg=Failed getting system information from $computer. $($_.Exception.Message)
Write-Error $msg
Write-Verbose Logging errors to $errorlog
$computer | Out-File -FilePath $Errorlog -append
} #Catch
#if there were no errors then $hash will exist and we can continue and
assume
#all other WMI queries will work without error
54
If ($hash) {
Write-Verbose Win32_Bios
$bios = Get-WmiObject -Class Win32_Bios -ComputerName $Computer
$hash.Add(SerialNumber,$bios.SerialNumber)
Write-Verbose Win32_OperatingSystem
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName
$Computer
$hash.Add(Version,$os.Version)
$hash.Add(ServicePackMajorVersion,$os.ServicePackMajorVersion)
#create a custom object from the hash table
$obj=New-Object -TypeName PSObject -Property $hash
#add a type name to the custom object
$obj.PSObject.TypeNames.Insert(0,MOL.ComputerSystemInfo)
causes
Write-Output $obj
#remove $hash so it isnt accidentally re-used by a computer that
#an error
Remove-Variable -name hash
} #if $hash
} #foreach
} #process
End {
Write-Verbose Ending Get-Computerdata
}
}
Update-FormatData prepend C:\CustomViewA.format.ps1xml
Get-ComputerData -ComputerName localhost
55
Lab B
Modify your advanced function Lab B from Chapter 10 so that the output object has the type name
MOL.DiskInfo. Then, create a custom view in a file named C:\CustomViewB.format.ps1xml. The
custom view should display objects of the type MOL.DiskInfo in a table format, displaying the information in a table as indicated in your design for this lab. Refer back to Chapter 6 for a refresher.
The column headers for the FreeSpace and Size properties should display FreeSpace(GB) and
Size(GB), respectively.
At the bottom of the script file, add these commands to test:
Update-FormatData prepend c:\CustomViewB.format.ps1xml
<function-name> -ComputerName localhost
Drive
----\\?\Volume{8130d5f3-8e9b-...
C:\Temp\
C:\
D:\
FreeSpace(GB)
------------0.07
9.78
2.72
2.72
Size(GB)
-------0.10
10.00
19.90
4.00
Note that the column headers are not exactly the same as the custom objects property names.
Sample format file solution
<?xml version=1.0 encoding=utf-8 ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>MOL.SystemInfo</Name>
<ViewSelectedBy>
<TypeName>MOL.DiskInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>18</Width>
</TableColumnHeader>
<TableColumnHeader/>
<TableColumnHeader>
<Label>FreeSpace(GB)</Label>
<Width>15</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Size(GB)</Label>
56
<Width>10</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>ComputerName</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Drive</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>FreeSpace</PropertyName>
</TableColumnItem>
<TableColumnItem>
<Propertyname>Size</Propertyname>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
Sample script solution
Function Get-VolumeInfo {
<#
.SYNOPSIS
Get information about fixed volumes
.DESCRIPTION
This command will query a remote computer and return information about fixed volumes. The function will ignore network, optical and other removable drives.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
.PARAMETER ErrorLog
Specify a path to a file to log errors. The default is C:\Errors.txt
.EXAMPLE
57
58
}
#create a custom object from the hash table
$obj=New-Object -TypeName PSObject -Property $hash
#Add a type name to the object
$obj.PSObject.TypeNames.Insert(0,MOL.DiskInfo)
Write-Output $obj
} #foreach
#clear $data for next computer
Remove-Variable -Name data
} #Try
Catch {
#create an error message
$msg=Failed to get volume information from $computer. $($_.Exception.Message)
Write-Error $msg
Write-Verbose Logging errors to $errorlog
$computer | Out-File -FilePath $Errorlog -append
}
} #foreach computer
} #Process
End {
Write-Verbose Ending Get-VolumeInfo
}
}
Update-FormatData prepend C:\CustomViewB.format.ps1xml
Get-VolumeInfo localhost
Lab C
Modify your advanced function Lab C from Chapter 10 so that the output object has the type name
MOL.ServiceProcessInfo. Then, create a custom view in a file named C:\CustomViewC.format.
ps1xml. The custom view should display objects of the type MOL.ServiceProcessInfo in a table format, displaying computername, service name, display name, process name, and process virtual size.
59
In addition to the table format, create a list view in the same file that displays the properties in
this order:
Computername
ProcessName
VMSize
ThreadCount
PeakPageFile
The final output should look something like this for the table.
ComputerName
-----------CLIENT2
CLIENT2
CLIENT2
CLIENT2
Service
------ AudioEndpo...
BFE
BITS
Browser
Displayname
----------
Windows Audio E...
Base Filtering ...
ackground Inte...
Computer Browser
ProcessName
VM
---------- -svchost.exe 172208128
svchost.exe
69496832
svchost.exe
499310592
svchost.exe 499310592
:
:
:
:
:
:
:
CLIENT2
AudioEndpointBuilder
Windows Audio Endpoint Builder
svchost.exe
172208128
13
83112
Note that per the design specifications from Chapter 6 not every object property is displayed by
default and that some column headings are different than the actual property names.
Sample format file solution:
<?xml version=1.0 encoding=utf-8 ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>MOL.SystemInfo</Name>
60
<ViewSelectedBy>
<TypeName>MOL.ServiceProcessInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>14</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Service</Label>
<Width>13</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>18</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>17</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>VM</Label>
<Width>14</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>ComputerName</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Displayname</PropertyName>
</TableColumnItem>
<TableColumnItem>
<Propertyname>ProcessName</Propertyname>
</TableColumnItem>
<TableColumnItem>
<Propertyname>VMSize</Propertyname>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
Chapter 12: Creating Custom Format Views
61
</View>
<View>
<Name>MOL.SystemInfo</Name>
<ViewSelectedBy>
<TypeName>MOL.ServiceProcessInfo</TypeName>
</ViewSelectedBy>
<ListControl>
<ListEntries>
<ListEntry>
<ListItems>
<ListItem>
<PropertyName>ComputerName</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Name</PropertyName>
<Label>Service</Label>
</ListItem>
<ListItem>
<PropertyName>Displayname</PropertyName>
</ListItem>
<ListItem>
<Propertyname>ProcessName</Propertyname>
</ListItem>
<ListItem>
<Propertyname>VMSize</Propertyname>
</ListItem>
<ListItem>
<Propertyname>ThreadCount</Propertyname>
</ListItem>
<ListItem>
<Propertyname>PeakPageFile</Propertyname>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
</ListControl>
</View>
</ViewDefinitions>
</Configuration>
62
to the file
.EXAMPLE
PS C:\> Get-ServiceInfo Server01
Run the command and query Server01.
.EXAMPLE
PS C:\> get-content c:\work\computers.txt | Get-ServiceInfo -logerrors
This expression will go through a list of computernames and pipe each name to
the command. Computernames that cant be accessed will be written to the log
file.
#>
[cmdletbinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$ComputerName,
[string]$ErrorLog=C:\Errors.txt,
[switch]$LogErrors
Chapter 12: Creating Custom Format Views
63
)
Begin {
Write-Verbose Starting Get-ServiceInfo
#if -LogErrors and error log exists, delete it.
if ( (Test-Path -path $errorLog) -AND $LogErrors) {
Write-Verbose Removing $errorlog
Remove-Item $errorlog
}
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting services from $computer
Try {
$data = Get-WmiObject -Class Win32_Service -computername $Computer
-Filter State=Running -ErrorAction Stop
foreach ($service in $data) {
Write-Verbose Processing service $($service.name)
$hash=@{
Computername=$data[0].Systemname
Name=$service.name
Displayname=$service.DisplayName
}
#get the associated process
Write-Verbose Getting process for $($service.name)
$process=Get-WMIObject -class Win32_Process -computername $Computer -Filter ProcessID=$($service.processid) -ErrorAction Stop
$hash.Add(ProcessName,$process.name)
$hash.add(VMSize,$process.VirtualSize)
$hash.Add(PeakPageFile,$process.PeakPageFileUsage)
$hash.add(ThreadCount,$process.Threadcount)
#create a custom object from the hash table
$obj=New-Object -TypeName PSObject -Property $hash
#add a type name to the custom object
$obj.PSObject.TypeNames.Insert(0,MOL.ServiceProcessInfo)
Write-Output $obj
64
} #foreach service
}
Catch {
#create an error message
$msg=Failed to get service data from $computer. $($_.Exception.
Message)
Write-Error $msg
if ($LogErrors) {
Write-Verbose Logging errors to $errorlog
$computer | Out-File -FilePath $Errorlog -append
}
}
} #foreach computer
} #process
End {
Write-Verbose Ending Get-ServiceInfo
}
}
Update-FormatData prepend C:\CustomViewC.format.ps1xml
Get-ServiceInfo -ComputerName localhost
Get-ServiceInfo -ComputerName localhost | format-list
65
66
13
Chapter
Lab Answers
Script and Manifest Modules
Note: Labs A, B, and C for Chapters 7 through 14 build upon what was accomplished in previous chapters. If you havent finished a lab from a previous
chapter, please do so. Then check your results with sample solutions on MoreLunches.com before proceeding to the next lab in the sequence.
In this chapter you are going to assemble a module called PSHTools, from the functions and custom
views that youve been working on for the last several chapters. Create a folder in the user module
directory, called PSHTools. Put all of the files you will be creating in the labs into this folder.
Lab A
Create a single ps1xml file that contains all of the view definitions from the 3 existing format
files. Call the file PSHTools.format.ps1xml. Youll need to be careful. Each view is defined by the
<View></View> tags. These tags, and everything in between should go between the <ViewDefinition></ViewDefinition> tags.
Here is a sample solution:
<?xml version=1.0 encoding=utf-8 ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>MOL.SystemInfo</Name>
<ViewSelectedBy>
<TypeName>MOL.ComputerSystemInfo</TypeName>
</ViewSelectedBy>
<ListControl>
<ListEntries>
<ListEntry>
<ListItems>
<ListItem>
<PropertyName>ComputerName</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Workgroup</PropertyName>
67
name>
</ListItem>
<ListItem>
<PropertyName>AdminPassword</PropertyName>
</ListItem>
<ListItem>
<Propertyname>Model</Propertyname>
</ListItem>
<ListItem>
<Propertyname>Manufacturer</Propertyname>
</ListItem>
<ListItem>
<Propertyname>SerialNumber</Propertyname>
<Label>BIOSSerialNumber</Label>
</ListItem>
<ListItem>
<Propertyname>Version</Propertyname>
<Label>OSVersion</Label>
</ListItem>
<ListItem>
<Propertyname>ServicePackMajorVersion</Property<Label>SPVersion</Label>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
</ListControl>
</View>
<View>
<Name>MOL.SystemInfo</Name>
<ViewSelectedBy>
<TypeName>MOL.DiskInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>18</Width>
</TableColumnHeader>
<TableColumnHeader/>
<TableColumnHeader>
<Label>FreeSpace(GB)</Label>
<Width>15</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Size(GB)</Label>
68
<Width>10</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>ComputerName</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Drive</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>FreeSpace</PropertyName>
</TableColumnItem>
<TableColumnItem>
<Propertyname>Size</Propertyname>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<View>
<Name>MOL.SystemInfo</Name>
<ViewSelectedBy>
<TypeName>MOL.ServiceProcessInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>14</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Service</Label>
<Width>13</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>18</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>17</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>VM</Label>
Chapter 13: Script and Manifest Modules
69
<Width>14</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>ComputerName</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Displayname</PropertyName>
</TableColumnItem>
<TableColumnItem>
<Propertyname>ProcessName</Propertyname>
</TableColumnItem>
<TableColumnItem>
<Propertyname>VMSize</Propertyname>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<View>
<Name>MOL.SystemInfo</Name>
<ViewSelectedBy>
<TypeName>MOL.ServiceProcessInfo</TypeName>
</ViewSelectedBy>
<ListControl>
<ListEntries>
<ListEntry>
<ListItems>
<ListItem>
<PropertyName>ComputerName</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Name</PropertyName>
<Label>Service</Label>
</ListItem>
<ListItem>
<PropertyName>Displayname</PropertyName>
</ListItem>
70
<ListItem>
<Propertyname>ProcessName</Propertyname>
</ListItem>
<ListItem>
<Propertyname>VMSize</Propertyname>
</ListItem>
<ListItem>
<Propertyname>ThreadCount</Propertyname>
</ListItem>
<ListItem>
<Propertyname>PeakPageFile</Propertyname>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
</ListControl>
</View>
</ViewDefinitions>
</Configuration>
Lab B
Create a single module file that contains the functions from the Labs A, B and C in Chapter 12,
which should be the most current version. Export all functions in the module. Be careful to copy the
function only. In your module file, also define aliases for your functions and export them as well.
Here is a sample solution:
#The PSHTools module file
Function Get-ComputerData {
<#
.SYNOPSIS
Get computer related data
.DESCRIPTION
This command will query a remote computer and return a custom object with system information pulled from WMI. Depending on the computer some information
may not be available.
.PARAMETER Computername
The name of a computer to query. The account you use to run this function
should have admin rights on that computer.
71
.PARAMETER ErrorLog
Specify a path to a file to log errors. The default is C:\Errors.txt
.EXAMPLE
PS C:\> Get-ComputerData Server01
Run the command and query Server01.
.EXAMPLE
PS C:\> get-content c:\work\computers.txt | Get-ComputerData -Errorlog c:\logs\
errors.txt
This expression will go through a list of computernames and pipe each name to
the command. Computernames that cant be accessed will be written to the log
file.
#>
[cmdletbinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$ComputerName,
[string]$ErrorLog=C:\Errors.txt
)
Begin {
Write-Verbose Starting Get-Computerdata
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting data from $computer
Try {
Write-Verbose Win32_Computersystem
$cs = Get-WmiObject -Class Win32_Computersystem -ComputerName $Computer -ErrorAction Stop
#decode the admin password status
Switch ($cs.AdminPasswordStatus) {
1 { $aps=Disabled }
2 { $aps=Enabled }
3 { $aps=NA }
4 { $aps=Unknown }
}
72
causes
Write-Output $obj
#remove $hash so it isnt accidentally re-used by a computer that
#an error
73
74
[string]$ErrorLog=C:\Errors.txt,
[switch]$LogErrors
)
Begin {
Write-Verbose Starting Get-VolumeInfo
}
Process {
foreach ($computer in $computerName) {
Write-Verbose Getting data from $computer
Try {
$data = Get-WmiObject -Class Win32_Volume -computername $Computer
-Filter DriveType=3 -ErrorAction Stop
Foreach ($drive in $data) {
Write-Verbose Processing volume $($drive.name)
#format size and freespace
$Size={0:N2} -f ($drive.capacity/1GB)
$Freespace={0:N2} -f ($drive.Freespace/1GB)
#Define a hashtable to be used for property names and values
$hash=@{
Computername=$drive.SystemName
Drive=$drive.Name
FreeSpace=$Freespace
Size=$Size
}
#create a custom object from the hash table
$obj=New-Object -TypeName PSObject -Property $hash
#Add a type name to the object
$obj.PSObject.TypeNames.Insert(0,MOL.DiskInfo)
Write-Output $obj
} #foreach
#clear $data for next computer
Remove-Variable -Name data
} #Try
Catch {
#create an error message
75
77
$hash.add(VMSize,$process.VirtualSize)
$hash.Add(PeakPageFile,$process.PeakPageFileUsage)
$hash.add(ThreadCount,$process.Threadcount)
#create a custom object from the hash table
$obj=New-Object -TypeName PSObject -Property $hash
#add a type name to the custom object
$obj.PSObject.TypeNames.Insert(0,MOL.ServiceProcessInfo)
Write-Output $obj
} #foreach service
}
Catch {
#create an error message
$msg=Failed to get service data from $computer. $($_.Exception.
Message)
Write-Error $msg
if ($LogErrors) {
Write-Verbose Logging errors to $errorlog
$computer | Out-File -FilePath $Errorlog -append
}
}
} #foreach computer
} #process
End {
Write-Verbose Ending Get-ServiceInfo
}
}
#Define some aliases
New-Alias -Name gcd
New-Alias -Name gvi
New-Alias -Name gsi
78
Lab C
Create a module manifest for the PSHTools that loads the module and custom format files. Test the
module following these steps:
1.
3.
2.
4.
5.
6.
@{
# Script module or binary module file associated with this manifest.
RootModule = .\PSHTools.psm1
# Version number of this module.
ModuleVersion = 1.0
# ID used to uniquely identify this module
GUID = 67afb568-1807-418e-af35-a296a43b6002
# Author of this module
Author = Don Jones & Jeff Hicks
# Company or vendor of this module
CompanyName = Month ofLunches
# Copyright statement for this module
Copyright = (c)2012 Don Jones and Jeffery Hicks
# Description of the functionality provided by this module
Description = Chapter 13 Module for Month of Lunches
79
VariablesToExport = *
# Aliases to export from this module
AliasesToExport = *
# List of all modules packaged with this module.
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData =
# HelpInfo URI of this module
# HelpInfoURI =
# Default prefix for commands exported from this module. Override the default
prefix using Import-Module -Prefix.
# DefaultCommandPrefix =
}
81
82
16
Chapter
Lab Answers
Making Tools that Make Changes
ShutDown 1
Restart 2
PowerOff 8
If the Force parameter is specified, add 4 to those values. So, if the command was Set-ComputerState computername localhost Action LogOff Force, then the value would be 4 (zero for
LogOff, plus 4 for Force). The execution of Win32Shutdown is what should be wrapped in the
implementing If block for WhatIf and Confirm support.
Here is a sample solution:
Function Set-Computerstate {
[cmdletbinding(SupportsShouldProcess=$True,ConfirmImpact=High)]
Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage=Enter a computername)]
[ValidateNotNullorEmpty()]
[string[]]$Computername,
[Parameter(Mandatory=$True,HelpMessage=Enter an action state)]
[ValidateSet(LogOff,Shutdown,Restart,PowerOff)]
83
[string]$Action,
[Switch]$Force
)
Begin {
Write-Verbose Starting Set-Computerstate
#set the state value
Switch ($Action) {
LogOff
{ $Flag=0}
ShutDown { $Flag=1}
Restart { $Flag=2}
PowerOff { $Flag=8}
}
if ($Force) {
Write-Verbose Force enabled
$Flag+=4
}
} #Begin
Process {
Foreach ($computer in $Computername) {
Write-Verbose Processing $computer
$os=Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computer
if ($PSCmdlet.ShouldProcess($computer)) {
Write-Verbose Passing flag $flag
$os.Win32Shutdown($flag)
}
} #foreach
} #Process
End {
Write-Verbose Ending Set-Computerstate
} #end
} #close function
Set-Computerstate localhost -action LogOff -WhatIf -Verbose
84
17
Chapter
Lab Answers
Creating a Custom Type Extension
Revisit the advanced function that you wrote for Lab A in Chapters 6 through 14 of this book.
Create a custom type extension for the object output by that function. Your type extension should
be a ScriptMethod named CanPing(), as outlined in this chapter. Save the type extension file as
PSHTools.ps1xml. Modify the PSHTools module manifest to load PSHTools.ps1xml, and then test
your revised module to make sure the CanPing() method works.
Here is a sample ps1xml file:
<?xml version=1.0 encoding=utf-8 ?>
<Types>
<Type>
<Name>MOL.ComputerSystemInfo</Name>
<Members>
<ScriptMethod>
<Name>CanPing</Name>
<Script>
Test-Connection -ComputerName $this.ComputerName -Quiet
</Script>
</ScriptMethod>
</Members>
</Type>
</Types>
85
</Type>
</Types>
Here is what the relevant part of the revised manifest might look like:
# Type files (.ps1xml) to be loaded when importing this module
TypesToProcess = .\PSHTools.ps1xml
# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = .\PSHTools.format.ps1xml
86
19
Chapter
Lab Answers
Troubleshooting Pipeline Input
Create a text file named C:\Computers.csv. In it, place the following content:
ComputerName
LOCALHOST
NOTONLINE
Be sure there are no extra blank lines at the end of the file. Then, consider the following command:
Import-CSV C:\Computers.txt | Invoke-Command Script { Get-Service }
The help file for Invoke-Command indicates that its ComputerName parameter accepts pipeline
input ByValue. Therefore, our expectation is that the computer names in the CSV file will be fed
to the ComputerName parameter. But if you run the command, that isnt what happens. Troubleshoot this command using the techniques described in this chapter, and determine where the computer names from the CSV file are being bound.
87
88
20
Chapter
Lab Answers
Create a new function in your existing PSHTools module. Name the new function Get-ComputerVolumeInfo. This functions output will include some information that your other functions already
produce, but this particular function is going to combine them all into a single, hierarchical object.
This function should accept one or more computer names on a ComputerName parameter. Dont
worry about error handling at this time. The output of this function should be a custom object with
the following properties:
ComputerName
OSVersion (Version from Win32_OperatingSystem)
SPVersion (ServicePackMajorVersion from Win32_OperatingSystem)
LocalDisks (all instances of Win32_LogicalDisk having a DriveType of 3)
Services (all instances of Win32_Service)
Processes (all instances of Win32_ProcessS)
The function will therefore be making at least four WMI queries to each specified computer.
Function Get-ComputerVolumeInfo {
[cmdletbinding()]
Param([parameter(Position=0,mandatory=$True,
HelpMessage=Please enter a computername)]#
[ValidateNotNullorEmpty()]
[string[]]$Computername
)
89
Process {
$params.Class=Win32_Service
Write-Verbose Getting data from $($params.class)
$services = Get-WmiObject @params
$params.Class=Win32_Process
Write-Verbose Getting data from $($params.class)
$procs = Get-WmiObject @params
$params.Class=Win32_LogicalDisk
Write-Verbose Getting data from $($params.class)
90
$params.Add(filter,drivetype=3)
$disks = Get-WmiObject @params
} #foreach computer
}
}
Get-ComputerVolumeInfo localhost
91
92
22
Chapter
Lab Answers
The .NET Framework contains a class named Dns, which lives within the System.Net namespace.
Read its documentation at http://msdn.microsoft.com/en-us/library/system.net.dns. Pay special attention to the static GetHostEntry() method. Use this method to return the IP address of www.
MoreLunches.com.
Function Resolve-HostIPAddress {
[cmdletbinding()]
Param (
[Parameter(Position=0,Mandatory=$True,
HelpMessage=Enter the name of a host. An FQDN is preferred.)]
[ValidateNotNullorEmpty()]
[string]$Hostname
)
93
Try {
$data=[system.net.dns]::GetHostEntry($hostname)
#the host might have multiple IP addresses
Write-Verbose Found $(($data.addresslist | measure-object).Count) address list entries
$data.AddressList | Select -ExpandProperty IPAddressToString
}
Catch {
Write-Warning Failed to resolve host $hostname to an IP address
}
94
23
Chapter
Lab Answers
Creating a GUI Tool, Part 1: The GUI
In this lab youre going to start a project that youll work with over the next few chapters, so youll
want to make sure you have a working solution before moving on. Developing a graphical PowerShell script is always easier if you have a working command-line script. Weve already done that
part for you in the following listing.
LISTING CH23-LABFUNCTION
You can either retype or download the script from MoreLunches.com.
The function takes a computer name as a parameter and gets services via WMI based on usersupplied filter criteria. The function writes a subset of data to the pipeline. From the command line
it might be used like this:
Get-servicedata $env:computername -filter running | Out-GridView
Your task in this lab is to create the graphical form using PowerShell Studio. You should end up
with something like the form shown in figure 23.7.
Make the Running radio button checked by default. Youll find it easier later if you put the radio
buttons in a GroupBox control, plus it looks cooler. The script youre creating doesnt have to do
anything for this lab except display this form.
ANSWER - see code listing from MoreLunches.com Chapter 23.
95
96
24
Chapter
Lab Answers
Creating a GUI Tool, Part 2: The Code
In this lab youre going to continue where you left off in chapter 23. If you didnt finish, please do
so first or download the sample solution from MoreLunches.com. Now you need to wire up your
form and put some actions behind the controls.
First, set the Computername text box so that it defaults to the actual local computer name. Dont
use localhost.
TIP Look for the forms Load event function.
Then, connect the OK button so that it runs the Get-ServiceData function from the lab in chapter 23
and pipes the results to the pipeline. You can modify the function if you want. Use the form controls
to pass parameters to the function.
TIP You can avoid errors if you set the default behavior to search for running services.
You can test your form by sending output to Out-String and then Write-Host. For example, in your
form you could end up with a line like this:
<code to get data> | Out-String | Write-Host
In the next chapter youll learn better ways to handle form output.
ANSWER - see code listing from MoreLunches.com Chapter 24
97
98
25
Chapter
Lab Answers
Creating a GUI Tool, Part 3: The Output
Well keep things pretty simple for this lab. Using the PowerShell Studio lab project from chapter
24, add a RichTextBox control to display the results. Here are some things to remember:
Configure the control to use a fixed-width font like Consolas or Courier New.
The Text property must be a string, so explicitly format data as strings by usingOut-String.
Use the controls Clear() method to reset it or clear out any existing results.
If you need to move things around on your form, thats okay. You can download a sample solution
at MoreLunches.com.
ANSWER - see code listing from MoreLunches.com Chapter 25
99
100
26
Chapter
Lab Answers
Creating Proxy Functions
Create a proxy function for the Export-CSV cmdlet. Name the proxy function Export-TDF. Remove the Delimiter parameter, and instead hardcode it to always use Delimiter `t (thats a
backtick, followed by the letter t, in double quotation marks).
Work with the proxy function in a script file. At the bottom of the file, after the closing } of the
function, put the following to test the function:
Get-Service | Export-TDF c:\services.tdf
Run the script to test the function, and verify that it creates a tab-delimited file named c:\services.tdf.
ANSWER - see code listing from MoreLunches.com Chapter 26
101
102
27
Chapter
Lab Answers
Setting Up Constrained
Demoting Endpoints
Create a new, local user named TestMan on your computer. Be sure to assign a pass- word to the
account. Dont place the user in any user groups other than the default Users group.
Then, create a constrained endpoint on your computer. Name the endpoint ConstrainTest. Design
it to include only the SmbShare module and to make only the Get-SmbShare command visible
(in addition to a small core set of cmdlets like Exit-PSSession, Select-Object, and so forth). After
creating the session configura- tion, register the endpoint. Configure the endpoint to permit only
TestMan to con- nect (with Read and Execute permissions), and configure it to run all commands as
your local Administrator account. Be sure to provide the correct password for Administrator when
youre prompted.
Use Enter-PSSession to connect to the constrained endpoint. When doing so, use the Credential
parameter to specify the TestMan account, and provide the proper password when prompted. Ensure that you can run Get-SmbShare but not any other command (such as Get-SmbShareAccess).
103
104