Public/New-PSFunctionScaffolding.ps1
Function New-PSFunctionScaffolding { <# .SYNOPSIS Creates a new PowerShell function. .DESCRIPTION Creates a new PowerShell function and outputs the results to either the console or a file. .PARAMETER Name The name of the function to be created. Names should adhere to PowerShell naming conventions. .PARAMETER OutputPath The path to which to export the function. Folder paths and individual filenames are both supported. .PARAMETER Module Creates a function file inside the directory structure for a given module. If the module is not found in PowerShell because it is not in $ENV:PSModulePath, it will default to the present working directory. This function is designed to work with modules that have the following directory structure: - Root - Manifest.psd1 - Module.psm1 - Public - PublicFunction1.ps1 - PublicFunction2.ps1 - Private - PrivateFunction1.ps1 - PrivateFunction2.ps1 By default, function files will be placed in the Public Directory. This behavior can be controlled with the `-Visibility` parameter. If you wish to place a new function inside an existing monolithic .psm1 or .ps1 file, please use the `-OutputPath` parameter. .PARAMETER PassThru Returns output to the console in addition to producing a file. .PARAMETER Force Forces an overwrite of an existing file. .PARAMETER Append Appends to an existing file. Necessary when consolidating documentation from multiple files into one. .PARAMETER Visibility Controls whether the function will be placed in the Public or Private sub-directories of a given module. .EXAMPLE # Outputs the framework for a new function to the console New-PSFunctionScaffolding -Name Test-Function .EXAMPLE # Outputs the framework for a new function to a file with the same name New-PSFunctionScaffolding -Name Test-Function -OutputPath C:\Path\To\Module\Public .EXAMPLE # Outputs the framework for a new function to a file with a unique name New-PSFunctionScaffolding -Name Test-Function -OutputPath C:\Path\To\Module\Public\MyFunction.ps1 .EXAMPLE # Outputs the framework for a new function to an existing script New-PSFunctionScaffolding -Name Test-Function -OutputPath C:\Path\To\Scripts\MyExistingScript.ps1 -Append -Force .EXAMPLE # Outputs the framework for multiple new private functions to an existing module New-PSFunctionScaffolding -Name Test-Function,Test-Function2 -Module MyExistingModule -Visibility Private .NOTES - Use of improper naming conventions will cause this command to fail. This is by design and ensures proper naming standards are adhered to. #> [CmdletBinding(DefaultParameterSetName="__AllParameterSets")] PARAM ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [String[]]$Name, [Parameter(Mandatory=$false, Position=1, ParameterSetName="OutFile")] [ValidateNotNullOrEmpty()] [String]$OutputPath, [Parameter(Mandatory=$false, Position=1, ParameterSetName="Module")] [ValidateNotNullOrEmpty()] [String]$Module ) # Create dynamic parameters DynamicParam { IF ($PSCmdlet.ParameterSetName -eq "OutFile") { # Create runtine dictionary $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() # Define parameter attributes $Attribute = [System.Management.Automation.ParameterAttribute]@{ Mandatory = $false ParameterSetName = "OutFile" } # Create attribute collection $AttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]$Attribute # Create parameter object and add name, type, attribute collection $PassThruParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('PassThru',[Switch],$AttributeCollection) $PassThruParameter.Value = $false $PSBoundParameters['PassThru'] = $PassThruParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('PassThru',$PassThruParameter) # Create parameter object and add name, type, attribute collection $ForceParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('Force',[Switch],$AttributeCollection) $ForceParameter.Value = $false $PSBoundParameters['Force'] = $ForceParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('Force',$ForceParameter) # Create parameter object and add name, type, attribute collection $AppendParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('Append',[Switch],$AttributeCollection) $AppendParameter.Value = $false $PSBoundParameters['Append'] = $AppendParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('Append',$AppendParameter) return $ParamDictionary } IF ($PSCmdlet.ParameterSetName -eq "Module") { # Create runtine dictionary $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() # Define parameter attributes $Attribute = [System.Management.Automation.ParameterAttribute]@{ Mandatory = $false ParameterSetName = "Module" } # Create attribute collection $AttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]$Attribute # Add ValidateSet to attribute collection $AttributeCollection.Add([System.Management.Automation.ValidateSetAttribute]::new("Public","Private")) # Create parameter object and add name, type, attribute collection $VisibilityParameter = [System.Management.Automation.RuntimeDefinedParameter]::new('Visibility',[String],$AttributeCollection) $VisibilityParameter.Value = 'Public' $PSBoundParameters['Visibility'] = $VisibilityParameter.Value # Add parameter to runtime dictionary $ParamDictionary.Add('Visibility',$VisibilityParameter) return $ParamDictionary } } #region BEGIN Block BEGIN { # Transfer Dynamic Parameters to runtime $Append = $PSBoundParameters['Append'] $PassThru = $PSBoundParameters['PassThru'] $Force = $PSBoundParameters['Force'] $Visibility = $PSBoundParameters['Visibility'] # Locally scope ErrorActionPreference for predictable behavior of Try/Catch blocks inside the function $ErrorActionPreference = 'Stop' # Declare variables $FunctionBlock = Get-Content -Path "$PSScriptRoot/../Templates/Template_Function.ps1" -Raw } #endregion BEGIN Block #region PROCESS Block PROCESS { # Create output variable $Results = [System.Collections.ArrayList]::new() FOREACH ($Function in $Name) { #region Input Validation: Name #region Input Validation: Name: Conventions # Validate that the input string matches the Verb-Noun naming convention IF ($Function -notmatch "(?-i:^\w+-\w+$)") { Write-Error "$Function does not match PowerShell Verb-Noun naming conventions. Please choose a more appropriate name." -ErrorAction Continue Continue } #endregion Input Validation: Name: Conventions #region Input Validation: Name: Approved Verbs # Get list of approved verbs $ApprovedVerbs = (Get-Verb).Verb # Split function name to get verb $Verb = $Function.Split('-')[0] IF ($ApprovedVerbs -notcontains $Verb) { Write-Error "$Function verb '$Verb' does not match PowerShell approved verbs. For a list of approved verbs, please run Get-Verb." -ErrorAction Continue Continue } #endregion Input Validation: Name: Approved Verbs #region Input Validation: Name: Capitalization # Validate that the input string has proper capitalization IF ($Function -notmatch "(?-i:^[A-Z]\w+-[A-Z]\w+$)") { # Declare variables $CapitalizedName = @() # Split input string into segments $Segments = $Function.Split('-') # Force title case for each segment FOREACH ($Segment in $Segments) { # Convert to lowercase so titlecase function works properly $Segment = $Segment.ToLower() # Convert to titlecase $CapitalizedName += (Get-Culture).TextInfo.ToTitleCase($Segment) } # Rejoin segments and replace the function name supplied by the user $Function = $CapitalizedName -join "-" } #endregion Input Validation: Name: Capitalization #endregion Input Validation: Name #region Produce Output String $Results = "Function $Function`n" + $FunctionBlock #endregion Produce Output String #region Output Handling #region Output Handling: OutFile IF ($PSCmdlet.ParameterSetName -eq "OutFile") { IF ($OutputPath) { # Test whether output path is a file or directory SWITCH -Regex ($OutputPath) { #region Output Handling: File # Absolute regex madness "\.\w\w$|\.\w\w\w$|\.\w\w\w\w$" { $OutputFilename = $OutputPath.Split('\')[-1] $OutputDirectory = $OutputPath.Substring(0,$OutputPath.LastIndexOf('\')) } #endregion Output Handling: File #region Output Handling: Folder Default { $OutputFilename = "$Function.ps1" $OutputDirectory = $OutputPath } #endregion Output Handling: Folder } } } #endregion Output Handling: OutFile #region Output Handling: Module ELSEIF ($PSCmdlet.ParameterSetName -eq "Module") { TRY { $OutputFilename = "$Function.ps1" $OutputDirectory = "$(Resolve-Path (Get-Module $Module -ListAvailable).ModuleBase)\$Visibility" } CATCH { Write-Warning "Unable to determine function directory. Defaulting to present working directory: $PWD" $OutputFilename = "$Function.ps1" $OutputDirectory = $PWD } } #endregion Output Handling: Module #region Output Handling: File Output IF ($PSCmdlet.ParameterSetName -match "OutFile|Module") { # Create path if it doesn't exist IF (!(Test-Path $OutputDirectory)) { New-Item -Path $OutputDirectory -ItemType Directory | Out-Null } # Output file $Results | Out-File $OutputDirectory\$OutputFilename -Encoding ascii -Force:$Force -Append:$Append } #endregion Output Handling: File Output #endregion Output Handling #region Results # Return results to the console if an output has not been specified or -Passthru has been selected IF(($PSCmdlet.ParameterSetName -notmatch "OutFile|Module") -or $Passthru) { Write-Output $Results } #endregion Results } } #endregion PROCESS Block #region END Block END { # Nothing to see here. Move along. } #endregion END Block } |