Skip to content

Instantly share code, notes, and snippets.

@jdhitsolutions
Last active March 8, 2023 12:52
Show Gist options
  • Save jdhitsolutions/65070cd51b5cfb572bc6375f67bcbc3d to your computer and use it in GitHub Desktop.
Save jdhitsolutions/65070cd51b5cfb572bc6375f67bcbc3d to your computer and use it in GitHub Desktop.
A proof of concept to add and get PowerShell meta data information
<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 01/27/2020 10:30:02 by BOVINE320\Jeff
-->
<Configuration>
<ViewDefinitions>
<View>
<!--Created 01/27/2020 10:30:02 by BOVINE320\Jeff-->
<Name>default</Name>
<ViewSelectedBy>
<TypeName>PSFunctionInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<!--Delete the AutoSize node if you want to use the defined widths.-->
<!-- <AutoSize /> -->
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>35</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Version</Label>
<Width>10</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Source</Label>
<Width>30</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Module</Label>
<Width>30</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<!--
By default the entries use property names, but you can replace them with scriptblocks.
<ScriptBlock>$_.foo /1mb -as [int]</ScriptBlock>
-->
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Version</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Source</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Module</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
#define an object class for the Get-PScriptInfo commmand
class PSFunctionInfo {
[string]$Name
[version]$Version
[string]$Description
[string]$Author
[string]$Source
[string]$Module
[string]$CompanyName
[string]$Copyright
[guid]$Guid
[string[]]$Tags
[datetime]$LastUpdate
[string]$Commandtype
PSFunctionInfo($Name, $Source) {
$this.Name = $Name
$this.Source = $Source
}
PSFunctionInfo($Name,$Author, $Version,$Source,$Description,$Module,$CompanyName,$Copyright,$Tags,$Guid,$LastUpdate,$Commandtype) {
$this.Name = $Name
$this.author = $Author
$this.Version = $Version
$this.Source = $Source
$this.Description = $Description
$this.Module = $Module
$this.CompanyName = $CompanyName
$this.Copyright = $Copyright
$this.Tags = $Tags
$this.guid = $Guid
$this.LastUpdate = $LastUpdate
$this.CommandType = $Commandtype
}
}
Function New-PSFunctionInfo {
#insert some metadata into a function
<# PSFunctionInfo
Version 1.1.0
Author Jeffery Hicks
CompanyName JDH Information Technology Solutions, Inc.
Copyright 2020
Description Create function metadata
Guid 23fa4b13-d303-4d2f-8b99-4d3038db3365
Tags Function,metadata
LastUpdate 01/25/2020 17:01:40
Source C:\scripts\PSFunctionInfo.ps1
#>
[cmdletbinding(SupportsShouldProcess)]
Param(
[Parameter(Position = 0, Mandatory, HelpMessage = "Specify name of the function")]
[ValidateNotNullOrEmpty()]
[string]$Name,
[Parameter(Mandatory, HelpMessage = "Specify the path that contains the function")]
[ValidateNotNullOrEmpty()]
[ValidateScript( {Test-Path $_})]
[string]$Path,
[string]$Author = [System.Environment]::UserName,
[string]$CompanyName,
[string]$Copyright,
[string]$Description,
[string]$Version = "1.0.0",
[guid]$Guid = $(([guid]::NewGuid()).guid),
[string[]]$Tags,
[datetime]$Updated = $(Get-Date),
[Parameter(HelpMessage = "Copy the metadata to the clipboard. The file is left untouched.")]
[alias("clip")]
[switch]$ToClipboard
)
Begin {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $Name in $Path with this metadata"
$info = @"
<# PSFunctionInfo
Version $Version
Author $Author
CompanyName $CompanyName
Copyright $Copyright
Description $Description
Guid $Guid
Tags $($Tags -join ",")
LastUpdate $Updated
Source $(Convert-Path $Path)
#>
"@
Write-Verbose $info
if ($ToClipboard) {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Copying to metadata to clipboard"
if ($pscmdlet.shouldprocess("function metadata", "Copy to clipboard")) {
Set-Clipboard -value $info
}
}
else {
#get the contents of the script file
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting the file contents"
$c = Get-Content -Path $path
#find the line that begins the function
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Finding the line containing Function $Name"
$m = $c | Select-String "Function $Name"
if ($m.Line) {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Found on line $($m.linenumber)"
$ln = $m.LineNumber - 1
#create a temporary file
$tmp = [System.IO.Path]::GetTempFileName()
#copy lines of the file up to the function definition line
$c[0..$ln] | Out-File -FilePath $tmp -whatIf:$false
#add the function info metadata
$Info | Out-File -FilePath $tmp -Append -whatif:$False
#go to the next line
$ln++
#copy the rest of the file to the temp file
$c[$ln..($c.Length)] | Out-File -FilePath $tmp -Append -whatif:$false
#copy the temp file to the new file
if ($pscmdlet.shouldprocess($Path, "Update function info for $Name")) {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Writing updated file"
Copy-Item -Path $tmp -Destination $path -Force
}
}
else {
Write-Warning "Could not find the function $name in $Path."
}
} #else process the file
} #process
End {
#clean up the temp file
If ($tmp -AND (Test-Path $tmp)) {
Remove-Item $tmp -whatif:$False
}
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"
} #end
} #close New-PSFunctionInfo
Function Get-PSFunctionInfo {
#get metadata from loaded functions
<# PSFunctionInfo
Version 2.1.0
Author Jeffery Hicks
CompanyName JDH Information Technology Solutions, Inc.
Copyright 2020
Description get function metadata
Guid 79fba8b7-6323-4ffa-abf8-25537ef897a3
Tags Function,metadata
LastUpdate 1/27/2020 12:23:52 PM
Source C:\scripts\PSFunctionInfo.ps1
#>
[cmdletbinding()]
[outputtype("PSFunctionInfo")]
Param(
[Parameter(Position = 0, HelpMessage = "Specify the name of a function. The default is all",ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]$Name = "*"
)
Begin {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"
#a regex pattern that will be used to parse the metadaa from the function definition
[regex]$rx = "(?<property>\w+)\s+(?<value>.*)"
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting loaded function(s): $Name"
$functions = Get-ChildItem -path Function:\$Name
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Found $($functions.count) functions"
Foreach ($fun in $functions) {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $($fun.name)"
$definition = $fun.definition -split "`n"
$m = $definition | Select-String -Pattern "#(\s+)?PSFunctionInfo"
if ($m.count -gt 1) {
Write-Warning "Multiple matches found for PSFunctionInfo. Will only process the first one."
}
if ($m) {
#get the starting line number
$i = $m[0].LineNumber
$meta = While ($definition[$i] -notmatch "#\>") {
$raw = $definition[$i]
if ($raw -match "\w+") {
$raw
}
$i++
}
#Define a hashtable that will eventually become a custom object
$h = @{
Name = $fun.name
CommandType = $fun.CommandType
Module = $fun.Module
}
#parse the metadata using regular expressions
for ($i = 0; $i -lt $meta.count; $i++) {
$groups = $rx.Match($meta[$i]).groups
$h.add($groups[1].value, $groups[2].value.trim())
}
#check for required properties
if (-Not ($h.ContainsKey("Source"))) {
$h.add("Source","")
}
if (-Not ($h.ContainsKey("version"))) {
$h.add("Version", "")
}
$h | Out-String | Write-Verbose
#write the custom object to the pipeline
$fi = New-Object -typename PSFunctionInfo -ArgumentList $h.name,$h.version
#update the object with hash table properties
foreach ($key in $h.keys) {
Write-Verbose "Updating $key"
$fi.$key = $h.$key
}
$fi
#clear the variable so it doesn't get reused
Remove-Variable m,h
} #if metadata found
else {
#insert the custom type name and write the object to the pipeline
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] No function metadata found for $($fun.name)."
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Creating a PSFunctionInfo object "
$fi = New-Object PSFunctionInfo -ArgumentList $fun.name,$fun.source
$fi.version = $fun.version
$fi.module = $fun.Module
$fi.Commandtype = $fun.CommandType
$fi.Description = $fun.Description
$fi
}
} #foreach
} #process
End {
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"
} #end
} #close Get-PSFunctionInfo
Update-FormatData $PSScriptRoot\PSFunctionInfo.format.ps1xml
Update-TypeData -appendpath $PSScriptRoot\PSFunctionInfo.types.ps1xml
<#
PS C:\> Get-PSFunctionInfo get* | Where author
Name Version Source Module
---- ------- ------ ------
Get-QOTD 1.0.0 C:\scripts\Get-QOTD.ps1
Get-Status 1.0.0 C:\scripts\getstat.ps1
Get-UTCString 1.0.0 C:\scripts\JDH-Functions.ps1
Get-DiskFree 1.0.0 C:\scripts\JDH-Functions.ps1
Get-LastBoot 1.0.0 C:\scripts\JDH-Functions.ps1
Get-MyFunctions 1.0.0 C:\scripts\JDH-Functions.ps1
Get-PSFunctionInfo 2.1.0 C:\scripts\PSFunctionInfo.ps1
PS C:\> get-psfunctioninfo get-qotd | select *
Name : Get-QOTD
Version : 1.0.0
Source : C:\scripts\Get-QOTD.ps1
CompanyName : JDH IT Solutions
Copyright : 2020
Description : Get a quote of the day
LastUpdate : 1/27/2020 11:46:19 AM
Module :
Author : Jeff
Guid : 16b5d672-4778-46d9-bbe5-08e7860e4e8a
Tags : {Web,profile}
Commandtype : Function
PS C:\> get-psfunctioninfo get-f*
Name Version Source Module
---- ------- ------ ------
Get-FileHash 3.1.0.0 Microsoft.PowerShell.Utility Microsoft.PowerShell.Utility
Get-Foo 1.0.0 d:\temp\foo.ps1
#>
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>PSFunctionInfo</Name>
<Members>
<PropertySet>
<Name>AuthorInfo</Name>
<ReferencedProperties>
<Name>Name</Name>
<Name>Version</Name>
<Name>Source</Name>
<Name>CompanyName</Name>
<Name>Copyright</Name>
<Name>Description</Name>
<Name>LastUpdate</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>DefaultDisplayPropertySet</Name>
<ReferencedProperties>
<Name>Name</Name>
<Name>Version</Name>
<Name>Source</Name>
<Name>Module</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</Type>
</Types>
@jdhitsolutions
Copy link
Author

The purpose of this code is to provide a way to get version and other metadata information for functions loaded into your PowerShell session that may not belong to a module. I have a number of stand-alone functions and I'd like to have version and source information. The code isn't concerned with loading, running or finding functions. It queries whatever is in the Function: psdrive. if the function belongs to a module, then I'll use the module version and source.

image

image

@jdhitsolutions
Copy link
Author

This code is a prototype for a suggestion I made for PowerShell 7 PowerShell/PowerShell#11667

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment