Last summer, Ed Wilson was looking for help with a small part of the book he was finishing up, Windows PowerShell 2.0 Best Practices. The topic he was working on was, “How do I know this script is safe to run?” Which is a great question and one with greater significance as more administrators come to PowerShell and are tempted to run scripts created elsewhere while not having perhaps all the experience and training to fully appreciate what might happen. I offered some comments on some things I would do. I also decided to write a script (trust me) that an administrator could run that would analyze a PowerShell script and produce a report, or profile, for that script.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The version I provided for the book was supposed to be clearly marked as a work in progress. I’ve worked with it on and off since then and it is still a work in progress, but I wanted to offer up the latest version for Ed’s readers and for the PowerShell community.
#REQUIRES -VERSION 2.0
<#
.Synopsis
This script will analyze a PowerShell script and create a script profile.
.Description
This script analyze a given Powershell script and prepare a script profile. Within this
profile you will see what parameters are called in the analyzed script, where they are called,
what PowerShell commands are executed, what functions are defined internally and if the script
is digitally signed.
The output is written to the pipeline as a document so that you can save it to a file for review.
If you use the -code parameter, the scripts contents will be included in the script profile.
If the analyzed script calls another script, references another script via dot sourcing, or loads
additional modules and snapins those references will NOT be analyzed, although you should see the
lines of code in the script profile so that you can analyze and review further.
.Parameter Script
The path to the .ps1 script file you want to profile.
.Parameter Code
If specified, then the script's code will be displayed at the end of the profile.
.Example
PS C:\> c:\scripts\get-scriptprofile foo.ps1 | Out-file FooProfile.txt
Save the basic script profile to a text file.
.Example
PS C:\> c:\scripts\get-scriptprofile foo.ps1 -code
Display the script profile along with the code from foo.ps1
.Example
PS C:\scripts> dir test*.ps1 | .\get-scriptprofile.ps1
Build a script profile for every PowerShell script that begins with Test.
.Inputs
Accepts strings as pipelined input
.Outputs
[object]
.Link
Get-Command
Get-AuthenticodeSignature
About_Functions
About_Parameters
About_Scripts
.Notes
NAME: Get-ScriptProfile.ps1
VERSION: 1.2
AUTHOR: Jeffery Hicks https://jdhitsolutions.com/blog
LASTEDIT: 1/14/2010
THIS IS A WORK IN PROGRESS...ESPECIALLY WITH POWERSHELL V2.0 SCRIPTS
DISCLAIMER AND WARNING:
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
TEST THOROUGHLY IN A NON-PRODUCTION ENVIRONMENT. IF YOU DON'T KNOW WHAT THIS
SCRIPT WILL DO...DO NOT RUN IT!
Microsoft PowerShell Source File -- Created with SAPIEN Technologies PrimalScript 2009
#>
[CmdletBinding()]
#define the parameters
Param (
[Parameter(
ValueFromPipeline=$True,
Position=0,
Mandatory=$True,
HelpMessage="Enter a the path and name to a PowerShell script")]
[String[]]$script,
[Parameter(
Mandatory=$False,
HelpMessage="Specify if you want to see the script's code.")]
[switch]$code
)
Begin {
$ErrorActionPreference="SilentlyContinue"
#get command information for all current aliaes, functions, filters,cmdlets
#and external scripts
$cmdHash=@{}
Get-Command -CommandType Alias,Function,Filter,ExternalScript,Cmdlet | foreach {
$cmdHash.add($_.name,$_.CommandType)
}
#define some variables
$Activity="Script Profiling"
} #end Begin Scriptblock
Process {
If (-Not (Test-Path $script)) {
Write-Warning "Cannot find $script."
return
}
$fullname=(Get-Item $script).Fullname
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Testing Signature" -PercentComplete 10
write `n
write $fullname.ToUpper()
write ("Digital Signature: {0}" -f (Get-AuthenticodeSignature $script).status)
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Parsing content" -PercentComplete 20
$data=Get-Command $fullname
$content=($data.scriptContents).Split("`n")
#get any requires
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Checking for requirements" -PercentComplete 30
write `n
write "`nRequires"
write "--------"
$content | where {$_.ToLower() -match "requires -"} | foreach {
#parse the requirement and get everything to the left of the first dash
$_.Substring($_.IndexOf("-")+1).ToLower()
} |select -unique
#strip out commented lines
$content=$content | where {$_ -notmatch "^\s*#"}
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Parsing Parameters" -PercentComplete 45
write "`nParameters"
write "----------"
$params=@()
$data.parameters.values | foreach {
$params+=$_.name
$_ | select Name,ParameterType
}
write "`nParameter usage"
write "---------------"
$content | foreach {
$line=$_
$params | foreach {
if ($_ -eq "?") {
$key="\$_"
[regex]$r="$key"
}
else {
$key="$_"
[regex]$r="\b$key\b"
}
if ($line -match $r) {
write $line.Trim()
} #end if
} #end foreach param
#select only unique lines to weed out
#multiple matches for the same line
} | select -Unique #end foreach line
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Parsing defined functions" -PercentComplete 60
write "`nInternally Defined Functions"
write "----------------------------"
$content | foreach {
if ($_ -match "function \w*.\w*") {$matches.values}
}
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Parsing commands" -PercentComplete 75
write "`nInvoked Commands"
write "----------------"
$content | foreach {
$line=$_
$cmdHash.keys | foreach {
#escape the ? which is a special regex character
if ($_ -eq "?") {
$key="\$_"
[regex]$r="$key"
}
else {
$key="$_"
[regex]$r="\b$key\b"
}
if ($line -match $r) {
write $line.Trim()
} #end if
} #end foreach key
} | select -unique #end foreach content line
If ($code) {
Write-Progress -Activity $Activity -Status "Analyzing $script" -CurrentOperation "Getting script contents" -PercentComplete 90
#write script contents if -code was specified
write "`nScript Contents"
write "---------------"
write $data.scriptcontents
}
} #end Process script block
End {
Write-Progress -Activity $Activity -Status "Done!" -CurrentOperation "Finished" -Completed -PercentComplete 100
} #end End script block
#end of script
The script, Get-ScriptProfile.ps1 requires PowerShell 2.0 and essentially is a giant string parser. It works pretty well with basic PowerShell 1.0 scripts. The basic syntax is to run the profile script and pass it a PowerShell script as a parameter.
PS C:\> c:\scripts\get-scriptprofile foo.ps1
The profiling script will report:
- Whether the script is signed and the state of the signature
- If a v2 script, any requirements
- What parameters the script takes
- The lines of the script where those parameters are used
- The names of any internally defined functions
- Any invoked PowerShell cmdlet, alias or function
All of this information is written to the pipeline more or less as a page but you can pipe the results to Out-File if you wish. Get-ScriptProfile also has a –code parameter which will append the script contents to the output.
The invoked commands section is perhaps the most useful. Within the script I use Get-Command to get all the currently available commands and then simply look for those commands throughout the script. If I was an admin analyzing a script, I’d be looking out for cmdlets that Stop or Remove.
This is far from a perfect and final tool, and perhaps it can’t be. The download version of the script has a short ToDo list. Many of the items are related to handling PowerShell 2.0 scripts. If you have a suggestion or solution, I hope you’ll let me know.
1 thought on “Profiling a Script”
Comments are closed.