Powershell attributes? - powershell

is it possible to assign some metadata Attributes to powershell macros? I have the set of macros and I want to encapsulate them to logical groups. I imagine something like:
[UnitTest]
function Do-Something()
{
...
}
and then pass all loaded macros in runtime and filter them out like:
$macrosInRuntime = Get-Item function:
$unitTestMacros = $macrosInRuntime |
? {$_.Attributes -contain "UnitTest"} # <-- ???
foreach ($macro in $unitTestMacros)
{
....
}
I will be greatful for any help

Interesting question... There are no such attributes of functions, AFAIK. But I think there is a half-hacky way that uses comment-based help attributes (perhaps not even hacky at all but I am not quite sure).
<#
.FUNCTIONALITY
TEST1
#>
function Do-Something1
{}
<#
.FUNCTIONALITY
TEST2
#>
function Do-Something2
{}
Get-ChildItem Function: | %{
$fun = $_.Name
try {
Get-Help $fun -Functionality TEST* | %{
switch($_.Functionality) {
'TEST1' { "$fun is for test 1" }
'TEST2' { "$fun is for test 2" }
}
}
}
catch {}
}
Output:
Do-Something1 is for test 1
Do-Something2 is for test 2
Perhaps this approach might be useful in some scenarios.
See also the section COMMENT-BASED HELP KEYWORDS in help:
man about_Comment_Based_Help
UPDATE
Though the answer above is accepted, I am still not quite happy with it. Here is yet another approach that is definitely not hacky. It also has an advantage, see the comments. This approach uses extra aliases with conventional names.
# Functions to be used in tests, with any names
function Do-Something1 { "Something1..." }
function Do-Something2 { "Something2..." }
# Aliases that define tests, conventional names are UnitTest-*.
# Note: one advantage is that an alias can be defined anywhere,
# right where a function is defined or somewhere else. The latter
# is suitable in scenarios when we cannot modify the source files
# (or just do not want to).
Set-Alias UnitTest-Do-Something1 Do-Something1
Set-Alias UnitTest-Do-Something2 Do-Something2
# Get UnitTest-* aliases and extract function names for tests.
Get-Alias UnitTest-* | Select-Object -ExpandProperty Definition
# Or we can just invoke aliases themselves.
Get-Alias UnitTest-* | % { & $_}

Organizing and grouping commands is an ongoing dilemma in PowerShell. It is something that will always have to be managed. However, there are some best practices around naming cmdlets and functions that can work if you are diligent. You probably have noticed that all cmdlets are in the verb-noun format. IE Get-Process, Set-Item, etc. What a lot of people do is add a third part to the command which groups the nouns together. For example, in the world of Active Directory, you don't have get-user, but rather get-aduser.
One thing you could do, and it may not be the prettiest thing, is to name your unit test functions with some 2 or 3 letter sequence of your choosing. Let's say you chose something incredibly original like UT for unit test. Your function would then be
function Do-UTSomething { "Something was done" }
Once you have all your UT functions, you can use the Get-Command cmdlet to iterate through them like so
Get-Command *UT* -commandtype function
Also, if you go a bit further and package them into a module, you could do one better and sort by that module.
Get-Command -module MyUnitTest
You can get all kinds of information on modules by using
help about_modules

Related

How to use line breaks in PowerShell Method-Chaining

I am trying to use a Retry Service that is written in the fluent-api pattern.
The methods return the service and allow a chaining of methods.
However even though i am using --> ` <-- i see a lot of errors as shown below.
Is there any workaround or other possibility to not write everything into one line?
(I already checked the methods names and return types)
(Entry point of RetryService)
Unfortunately about_Methods doesn't seem to have a clarification on method chaining and its parsing rules. If you want to chain multiple methods on new lines, the dot . has to be at the end of each statement then a newline is allowed. The backticks are not needed.
In example:
[powershell]::Create().
AddScript({ "hello $args" }).
AddArgument('world').
Invoke()
Another way to chain method calls is to use the ForEach-Object command (alias %). This relies on the parameter set with the -MemberName parameter (implicitly by passing a string as the 1st argument).
PowerShell 7+ even lets you write the pipe symbol | on a new line:
[powershell]::Create()
|% AddScript { "hello $args" }
|% AddArgument 'world'
|% Invoke
If there are multiple method arguments, you have to separate them by , (parentheses are unnecessary).
For PS 5 and below you have to use a slightly different syntax because the pipe symbol has to be on the same line as the previous command:
[powershell]::Create() |
% AddScript { "hello $args" } |
% AddArgument 'world' |
% Invoke
Is this a better way than using member access operator .? I don't think so, it's just a different way. IMO it does look more consistent compared to regular PowerShell commands. Performance might be even worse than . but for high level code it propably doesn't matter (I haven't measured).

PowerShell $_ syntax

In this answer the author proposed the following snippet:
dir -Path C:\FolderName -Filter *.fileExtension -Recurse | %{$_.FullName}
I can understand the majority of it, but I'm unable to search documentation for the last part. The output of the search is piped | and used in %{} and as $_.
I have experimented around it, %{} is a for-each statement I believe, bing search was not effective. $_ is also somewhat magic: it is a variable, with no name and thus immediately consumed? I don't care much for the .FullName, that part I sorted out. Again, bing search was not effective, nor searching for those char sequences in PowerShell docs.
Can anybody explain it to me?
%{} is not "a thing" - it's two things: % and {}
% is an alias for the ForEach-Object cmdlet:
PS ~> Get-Alias '%'
CommandType Name Version Source
----------- ---- ------- ------
Alias % -> ForEach-Object
... so it resolves to:
... |ForEach-Object { $_.FullName }
ForEach-Object is basically PowerShell's map function - it takes input via the pipeline and applies the operation described in the {} block to each one of them.
$_ is an automatic reference to the current pipeline input item being processed
You can think of it a bit like a foreach($thing in $collection){} loop:
1..10 |ForEach-Object { $_ * 10 }
# produces the same output as
foreach($n in 1..10){
$n * 10
}
Except we can now stick our loop in the middle of a pipeline and have it produce output for immediate consumption:
1..10 |ForEach-Object { $_ * 10 } |Do-SomethingElse
ForEach-Object is not the only thing that makes use of the $_ automatic variable in PowerShell - it's also used for pipeline-binding expressions:
mkdir NewDirectory |cd -Path { $_.FullName }
... as well as property expressions, a type of dynamic property definition supported by a number of cmdlets like Sort-Object:
1..10 |Sort-Object { -$_ } # sort in descending order without specifying -Descending
... Group-Object:
1..10 |Group-Object { $_ % 3 } # group terms by modulo congruence
... and Select-Object:
1..10 |Select-Object #{Name='TimesTen';Expression={$_ * 10}} # Create synthetic properties based on dynamic value calculation over input
To complement Mathias' answer, which explains the specific constructs well, with how you could / couldn't have discovered this information yourself, using PowerShell's own help system:
Relevant help topics and use of the help system:
Note: To get an overview of all aspects of PowerShell's help system, simply run help.
% is a built-in alias for the ForEach-Object cmdlet:
Use Get-Help ForEach-Object to view the help topic in the terminal.
If no local topics are found, you must download them via the Update-Help cmdlet.
Tips:
Add the -Online switch to open the (potentially more current) online version of the topic in your browser.
You can bootstrap your use of Get-Help with Get-Help Get-Help (or even help help):
Cmdlet-specific help comes in detail levels: terse (default, shows the syntax and overview description only), -Detailed (includes parameter descriptions and example commands) and -Full (additionally includes technical parameter information and extended notes).
-Examples can be used to show example commands only.
With keyword-based search (see below), you can limit results to topics of a certain category with the -Category parameter.
For convenience, you can also use the built-in help function, which wraps Get-Help calls with display paging (simply put: by piping the output to the more utility) and defaults to detail level -Full.
{...} is a script block literal, a block of arbitrary PowerShell code that can be invoked on demand:
help about_Script_Blocks shows the topic locally; the about_ prefix indicates that the topic is a conceptual help topic (rather than one covering a specific command); when you use Get-Help to search for a keyword (see below), you can (somewhat obscurely) limit the results to conceptual topics with -Category HelpFile.
Note: As of this writing, about_ topics can not yet be directly viewed online by adding -Online - see GitHub issue #13550 - but it's easy to google them by name.
$_ is a variable, as the $ sigil followed by an identifier implies, and is more specifically an automatic (built-in) variable:
help about_Variables covers variables in general.
help about_Automatic_Variables covers the automatic ones.
How the above can / cannot be discovered based on symbols and aliases alone:
Doing a web search for symbols is notoriously unhelpful.
As an aside: Running distinct syntax constructs such as % and { ... } together without whitespace between them (e.g. %{$_.FullName}) constitutes an additional barrier, and should therefore be avoided.
Narrowing your search by using only PowerShell's help system helps, but only to a limited degree:
%
Because Get-Help is aware of aliases, help % actually works fine and directly shows ForEach-Object's help topic.
help % -Examples shows example commands that include the use of script blocks and the automatic $_ variable.
Even though Get-Help supports keyword-based search, searching for symbol-based terms {} and $_ directly isn't helpful, because even when limiting the search to conceptual (about_-prefixed topics) with -Category HelpFile, there are either too many hits (help '$_' -Category HelpFile) or the relevant topic doesn't show at all (help '{}' -Category HelpFile)
$_ can be discovered indirectly, IF you already know that it is an instance of a variable:
help variables -Category HelpFile happens to take you directly to the relevant (local) about_Automatic_Variables topic,
whereas help variable -Category HelpFile lists the following matching topics about_Variable_Provider, ``, about_Automatic_Variables, about_Preference_Variables, about_Remote_Variables, about_Variables, and about_Environment_Variables
Note: Thanks to PowerShell's pervasive support for wildcard expressions, you could have performed the search also as follows: help about*variable* - be sure to enclose both sides of the search term in *.
$_ can be discovered indirectly, IF you already know that it is an instance of a script (code) block:
help about_*block* takes you directly to the relevant (local) about_Script_Blocks topic.
Potential future improvements:
It would be a great improvement if PowerShell's help system supported focused symbol-based searches.
Similarly, the ability to directly look up operators, such as -match, the regular-expression matching operator, would be helpful:
GitHub issue #11339 proposes just that.
On a related note, GitHub issue #11338 proposes adding the ability to look up documentation for .NET types (online).
This answer contains custom functions Show-OperatorHelp and Show-TypeHelp, which fill that gap for now (also available as Gists).

Get All Functions In A PowerShell Script

I have a problem similar to this question. I want to get all the functions in a given PowerShell script but the difference being I don't want to execute the contents of the script and I don't want to execute the functions.
The intent is to be able to load all the functions into the runspace to be able to pull the comment-based help from each function for documentation purposes.
Does anyone have any magical tricks to just load the functions from a .ps1 without executing all the other code within that file?
I thought about using [System.Management.Automation.PSParser]::Tokenize() to parse the script file but that's a whole lot more work than I would like to do. If someone has something easier, I'd be delighted.
# I want to load this to get the comment-based help
Function Invoke-Stuff {
<#
.SYNOPSIS
Stuff doer
.DESCRIPTION
It does lots of stuff
.EXAMPLE
Invoke-Stuff
#>
Write-Host "Stuff was done"
}
# But I don't want to execute any of this
$Items = Get-ChildItem
$Items | ForEach-Object {
Invoke-Stuff
}
The AST is the way to go for static(ish) analysis. Here's how I would do what you described
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
# Get the AST of the file
$tokens = $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
'MyScript.ps1',
[ref]$tokens,
[ref]$errors)
# Get only function definition ASTs
$functionDefinitions = $ast.FindAll({
param([System.Management.Automation.Language.Ast] $Ast)
$Ast -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
# Class methods have a FunctionDefinitionAst under them as well, but we don't want them.
($PSVersionTable.PSVersion.Major -lt 5 -or
$Ast.Parent -isnot [System.Management.Automation.Language.FunctionMemberAst])
}, $true)
# Add the functions into the runspace
$functionDefinitions | ForEach-Object {
$rs.SessionStateProxy.InvokeProvider.Item.Set(
'function:\{0}' -f $_.Name,
$_.Body.GetScriptBlock())
}
# Get help within the runspace.
$ps = [powershell]::Create().AddScript('Get-Help MyFunction')
try {
$ps.Runspace = $rs
$ps.Invoke()
} finally {
$ps.Dispose()
}
You could also use the $tokens from near the top if you want to go a purely static route. The comments won't be in the AST but they will be in the tokens.
Edit The method above actually loses comment help somewhere in the process, not because of the runspace but just because of how the function is assigned. Likely due to the comments not really being a part of the AST. In any case there is a more direct and more static way to obtain the help.
Instead of defining the functions, you can use the GetHelpContent method on FunctionDefinitionAst
$helpContent = $functionDefinitions | ForEach-Object { $_.GetHelpContent() }
This will return a CommentHelpInfo object for each function. It's important to note that this is not the same object returned by the Get-Help cmdlet. Most notably it does not distinguish between things like the code and the description in an example block. However, if you need the CBH to be parsed as normal you can get the comment block text and define your own fake version.
$helpContent = $functionDefinitions | ForEach-Object {
# Get the plain string comment block from the AST.
$commentBlock = $_.GetHelpContent().GetCommentBlock()
# Create a scriptblock that defines a blank version of the
# function with the CBH. You may lose some parameter info
# here, if you need that replace param() with
# $_.Body.ParamBlock.Extent.Text
$scriptBlock = [scriptblock]::Create(('
function {0} {{
{1}
param()
}}' -f $_.Name, $commentBlock))
# Dot source the scriptblock in a different scope so we can
# get the help content but still not pollute the session.
& {
. $scriptBlock
Get-Help $_.Name
}
}
Ideally, you'd have your functions in a module (or their own script file), which you could load. Your 'execution' script would then be it's own thing that you only run when you do want to execute the function, or you run the functions manually.
If you had your functions in a module, in one of the paths that PowerShell looks to for them, you'd be able to run this to see the functions:
Get-Command -Module Example -CommandType Function
Most of the time you wouldn't include the CommandType parameter, unless there was extra stuff in the module you didn't care about.
This module approach (or separation of function/execution) would be the only way to get your comment based help working as you'd expect it to work.
If you're happy to just see the names of your functions, you'd have to load the content of the script file, and check for lines starting with the function keyword. There's probably smarter ways to do this, but that's where my mind went to.
Just to be a bit more clear about what I'm getting at around separating the functions from the execution code, it'd look something like:
functions.ps1
# I want to load this to get the comment-based help
Function Invoke-Stuff {
<#
.SYNOPSIS
Stuff doer
.DESCRIPTION
It does lots of stuff
.EXAMPLE
Invoke-Stuff
#>
Write-Host "Stuff was done"
}
You can then freely load the function into your session, making the comment based help accessible.
execute.ps1
. .\path\functions.ps1
# But I don't want to execute any of this
$Items = Get-ChildItem
$Items | ForEach-Object {
Invoke-Stuff
}

Get all references to a given PowerShell module

Is there a way to find a list of script files that reference a given module (.psm1)? In other words, get all files that, in the script code, use at least 1 of the cmdlets defined in the module.
Obviously because of PowerShell 3.0 and above, most of my script files don't have an explicit Import-Module MODULE_NAME in the code somewhere, so I can't use that text to search on.
I know I can use Get-ChildItem -Path '...' -Recurse | Select-String 'TextToSearchFor' to search for a particular string inside of files, but that's not the same as searching for any reference to any cmdlet of a module. I could do a search for every single cmdlet in my module, but I was wondering if there is a better way.
Clarification: I'm only looking inside of a controlled environment where I have all the scripts in one file location.
Depending on the scenario, the callstack could be interesting to play around with. In that case you need to modify the functions which you want to find out about to gather information about the callstack at runtime and log it somewhere. Over time you might have enough logs to make some good assumptions.
function yourfunction {
$stack = Get-PSCallStack
if ($stack.Count -gt 1) {
$stack[1] # log this to a file or whatever you need
}
}
This might not work at all in your scenario, but I thought I throw it in there as an option.

Can I exclude a single function from Export-ModuleMember?

I have a large set of functions defined in a PowerShell script module. I want to use Export-ModuleMember -Function *, but I want to exclude just one function. It'll be easier for me to exclude this one function than to list all the included functions. Is there anyway to achieve this?
My stock answer about excluding functions is to use verb-noun naming for functions I want to export, and use initial caps for everything else.
Then, Export-ModuleMember -function *-* takes care of it.
Find all functions in a script and then filter based on what you want to exclude (assuming PowerShell v2):
$errors = $null
$functions = [system.management.automation.psparser]::Tokenize($psISE.CurrentFile.Editor.Text, [ref]$errors) `
| ?{(($_.Content -Eq "Function") -or ($_.Content -eq "Filter")) -and $_.Type -eq "Keyword" } `
| Select-Object #{"Name"="FunctionName"; "Expression"={
$psISE.CurrentFile.Editor.Select($_.StartLine,$_.EndColumn+1,$_.StartLine,$psISE.CurrentFile.Editor.GetLineLength($_.StartLine))
$psISE.CurrentFile.Editor.SelectedText
}
}
This is the technique I used for v2 to create a ISE Function Explorer. However, I don't see a reason why this won't work with plain text outside ISE. You need to workaround the caret line details though. This is just an example on how to achieve what you want.
Now, filter what is not required and pipe this to Export-ModuleMember!
$functions | ?{ $_.FunctionName -ne "your-excluded-function" }
If you are using PowerShell v3, the parser makes it a lot easier.
So I know this is way late to the party, but the simple solution is to place all functions you do not want exported AFTER the Export-ModuleMember cmdlet. Any Functions defined AFTER that statement will not be exported and WILL be available to your module (aka private functions).
Perhaps the more elegant method is to include a Module Definition file, and simply not include that function on the list of functions to include.
The idea of writing code inside the module to not include functions in the module seems overly complicated, this is not a new feature, I've been placing functions after Export since the very early days of PowerShell.
My solution, using PowerShell V3, as hinted by ravikanth (who was using V2 in his solution), is to define a PSParser module:
Add-Type -Path "${env:ProgramFiles(x86)}\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll"
Function Get-PSFunctionNames([string]$Path) {
$ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$null, [ref]$null)
$functionDefAsts = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)
$functionDefAsts | ForEach-Object { $_.Name }
}
Export-ModuleMember -Function '*'
And in a module, if I want to exclude a given function, the last line would look like:
Export-ModuleMember -Function ( (Get-PSFunctionNames $PSCommandPath) | Where { $_ -ne 'MyPrivateFunction' } )
Note that this will only work in PowerShell V3 or later because the AST parser and $PSCommandPath were introduced in version 3.