functions/public.ps1
#region define an object class for the Get-PSFunctionInfo 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 #this class has no methods #constructors 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 } } #endregion #TODO - Create a VSCode task? Function New-PSFunctionInfo { [cmdletbinding(SupportsShouldProcess)] [alias('npfi')] Param( [Parameter(Position = 0, Mandatory, HelpMessage = "Specify the name of the function")] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory, HelpMessage = "Specify the path that contains the function")] [ValidateNotNullOrEmpty()] [ValidateScript( { Test-Path $_ })] [ValidatePattern("\.ps1$")] [string]$Path, [string]$Author = [System.Environment]::UserName, [string]$CompanyName, [string]$Copyright = (Get-Date).Year, [string]$Description, [ValidateNotNullOrEmpty()] [string]$Version = "1.0.0", [string[]]$Tags, [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)" [guid]$Guid = $(([guid]::NewGuid()).guid) [string]$updated = Get-Date -Format g } #begin Process { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Creating 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 the 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 from $Path" $file = [System.Collections.Generic.list[string]]::New() Get-Content -Path $path | ForEach-Object { $file.add($_) } #find the function line Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Searching for Function $Name" $find = $file | Select-String -Pattern "^(\s+)?(F|f)unction $name(\s+|\{)?" if ($find.count -gt 1) { Write-Warning "Detected multiple matches for Function $name in $Path. Unable to insert metadata." #bail out return } elseif ($find.count -eq 1) { #$index = $file.findIndex( { $args[0] -match "^(\s+)?Function $name(\s+|\{)" }) #the index for the file list will be 1 less than the pattern match $index = $find.Linenumber - 1 } else { Write-Warning "Failed to find a function called $Name in $path." return } #find the opening { for the function Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting the position of the opening {" $i = $index do { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Testing index $i" if ($file[$i] -match "\{") { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Found opening { at $i" $found = $True } else { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] incrementing index" $i++ } } until ($found -OR $i -gt $file.count) if ($i -gt $file.count) { Write-Warning "Failed to find the opening { for Function $Name." return } #test for an existing PSFunctionInfo entry in the next 5 lines if ($file[$i..($i + 5)] | Select-String -Pattern "PSFunctionInfo" -Quiet) { Write-Warning "An existing PSFunctionInfo entry has been detected." } else { # insert after the opening { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Inserting metadata at position $($i+1)" $file.Insert(($i + 1), $info) #write the new data to the file Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Updating $path" $file | Set-Content -Path $Path } } #else process the file } #process End { Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)" } #end } #close New-PSFunctionInfo #get function info from non-module functions Function Get-PSFunctionInfo { [cmdletbinding(DefaultParameterSetName = "name")] [outputtype("PSFunctionInfo")] [alias("gpfi")] Param( [Parameter( Position = 0, HelpMessage = "Specify the name of a function that doesn't belong to a module.", ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = "name" )] [string]$Name = "*", [Parameter(HelpMessage = "Specify a tag")] [string]$Tag ) Begin { Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)" #a regex pattern that will be used to parse the metadata from the function definition [regex]$rx = "(?<property>\w+)\s+(?<value>.*)" } #begin Process { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using parameter set $($pscmdlet.ParameterSetName)" Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting function $Name" # filter out functions with a module source and that pass the private filtering test $functions = (Get-ChildItem -Path Function:\$Name).where( { -Not $_.source -And (test_functionname $_.name) }) Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Found $($functions.count) functions" Foreach ($fun in $functions) { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] $($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 in $($fun.name). 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 "[$((Get-Date).TimeofDay) PROCESS] Updating $key" $fi.$key = $h.$key } if ($tag) { Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Filtering for tag $tag" # write-verbose "$($fi.name) tag: $($fi.tags)" if ($fi.tags -match $tag) { $fi } } else { $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] Creating a new and temporary 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 #Write the object depending on the parameter set and if it belongs to a module AND has a source if (-Not $tag) { $fi } } } #foreach } #process End { Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)" } #end } #close Get-PSFunctionInfo Function Get-PSFunctionInfoTag { [cmdletbinding()] [outputtype("String")] Param() Write-Verbose "[$((Get-Date).TimeofDay)] Starting $($myinvocation.mycommand)" $taglist = [System.Collections.Generic.list[string]]::new() Write-Verbose "[$((Get-Date).TimeofDay)] Getting unique tags from Get-PSFunctionInfo" $items = (Get-PSFunctionInfo -ErrorAction stop).tags | Select-Object -Unique if ($items.count -eq 0) { Write-Warning "Failed to find any matching functions with tags" } else { Write-Verbose "[$((Get-Date).TimeofDay)] Found at least $($items.count) tags" foreach ($item in $items) { if ($item -match ",") { #split strings into an array $item.split(",") | ForEach-Object { if (-Not $taglist.contains($_)) { $taglist.add($_) } } } #if an array of tags else { if (-Not $taglist.contains($item)) { $taglist.add($item) } } } #foreach item } #else #write the list to the pipeline $taglist | Sort-Object Write-Verbose "[$((Get-Date).TimeofDay)] Ending $($myinvocation.mycommand)" } |