pf-module.ps1

$ErrorActionPreference = 'stop'

function Initialize-File_InNotPresent($path) {
    if (-not (Test-Path -Path $path)) {
        Set-Content -Path $path -Value ""
    }
}

function Remove-Alias {
    Param(
        [Parameter(ValueFromPipeline=$true)]$name,
        [Switch]$NoResolvedCommand
    )
    process {
        If ($name -and ( Test-Path Alias:$name)) {Remove-Item Alias:$name}
    }
    end {
        if ($NoResolvedCommand) {
            $toRemove = Get-Alias | Where-Object { -not $_.ResolvedCommand }
            $toRemove.name | Where-Object {$_} | Where-Object { Test-Path Alias:$_ } | 
                ForEach-Object { Remove-Item Alias:$_ -Verbose }
        }
    }
}
function Remove-Alias:::Example {
    Remove-Alias -NoResolvedCommand
}

function Get-Functions {
    param( 
        [Parameter(ValueFromPipeline=$true)]$path
    ) 
    process {
        $path = $path.FullName ?? $path
        . $path
        return Get-Command | Where-Object ScriptBlock |
            Where-Object { $_.ScriptBlock.File -eq $path } 
    }
}

function Get-ContentToInclude($functionName) {
    if ($functionName) {
        $header = get-command DefineModuleHeader
        $body = $header.ScriptBlock.ToString()
        $result = "#Include-Start $functionName`n$body`n#Include-end $functionName`n"
    }
    return $result
}

function Install-Module_BlockScope {
    Param(
        [string[]]$Name,
        [string[]]$repository = $Global:PSRepositoryLocal,
        [ScriptBlock]$script
    )
        if ($Name) {
            $missingModules = $Name | ForEach-Object { if (-not (Get-Module $_ -ListAvailable -SkipEditionCheck)) { $_ } }
        }
        try {
            if ($missingModules) {
                $moduleToRemove = $missingModules | ForEach-Object {
                    $repositorySelected = if ($repository) { $repository } else {
                        $candidateModules = find-module $_ | Sort-Object PublishedDate -Descending
                            | Select-Object -First 1
                        $candidateModules.Repository   
                    } 
                    Install-Module -Name $_ -Repository $repositorySelected -PassThru -AllowClobber 
                }
            }
            $script.InvokeReturnAsIs()
        }
        finally {
            if ($moduleToRemove) {
                $moduleToRemove | Uninstall-Module
            }
        }
}
function Install-Module_BlockScope:::Test {
    $moduleName = "pf-basic"
    Get-Module -Name $moduleName -ListAvailable | should -BeNullOrEmpty 
    $result = Install-Module_BlockScope -Name $moduleName -script {
        $mm = Get-Module -Name $moduleName -ListAvailable 
        $mm | should -not -BeNullOrEmpty
        Write-Host $mm
        Write-Output "Imported"
    }.GetNewClosure()
    write-host $result
    Get-Module -Name $moduleName -ListAvailable | should -BeNullOrEmpty 
}

function Remove-PowershellComments($path) {
    $content = Get-Content -Path $path -Raw
    $result = Remove-PowershellCommentsFromString -content $content
    Set-Content -Path $path -Value $result -NoNewline
}

function Remove-PowershellCommentsFromString($content) {
    $result = $content -replace '\s*#(?!\brequires\b).*', ''   
    $result = $result.Trim()
    return $result
}

function Remove-PowershellCommentsFromString:::Test($content) {
    Remove-PowershellCommentsFromString -content "#comment" | assert -eq ""
    Remove-PowershellCommentsFromString -content "#requires" | assert -eq "#requires"
    Remove-PowershellCommentsFromString -content " #comment abc" | assert -eq ""
    Remove-PowershellCommentsFromString -content "abc" | assert -eq " abc"
}

function Add-Module_PSModulePath ([string]$ModulesFolder = 'PSModules', [switch]$notRequired ) {
    $p = split-path -parent ($current = ( Get-PSCallStack )[0].ScriptName )
    while ( $p -and -not ( Test-Path ( "$p\$ModulesFolder" ) ) ) { $p = Split-Path $p -Parent }
    if (-not $p -and -not $notRequired) { 
        throw "Modules folder '$ModulesFolder' cannot be found starting from '$current'" 
    } 
    $p = Resolve-Path "$p\$ModulesFolder"
    if (-not $env:PSModulePath.Contains("$p;")) { 
        $env:PSModulePath = "$p;$env:PSModulePath" 
    }
}

function Get-Modulefolders ($path) {
    $folders =  if ($path) { Get-ChildItem -Directory -path $path } 
                    else { Get-ChildItem -Directory }
    $folders |  Where-Object name -NotLike '.*'
}

function Sync-ModuleManifests([switch]$all) {
    $commitCount =  Get-GitCommitCount
    $folders = Get-Modules_Local | Get-Path 
    if (-not $all) {
        $folders = $folders | Get-Git_Changed -folder
    }
    $folders | Initialize-Module_Manifest_EnsureFiles -Revision $commitCount
}

function Publish-ModuleEx {
    param(
        [Parameter(ValueFromPipeline=$true)]$modulePath,
        [string]$NuGetApiKey,
        [string[]]$repositoryList = @('LocalRepo')
    ) 
    process {
        $moduleName = Split-Path $modulePath -Leaf
        $psd = Import-PowerShellDataFile  -Path "$modulePath\$moduleName.psd1" 
     
        foreach ($repository in $repositoryList) {
            $module = Find-Module  -Name $moduleName -RequiredVersion $psd.ModuleVersion `
                            -ErrorAction SilentlyContinue -Repository $repository
            if  (-not $module) {
                $additionalParams = @{}
                if ($NuGetApiKey) {
                    $additionalParams.Add("NuGetApiKey",$NuGetApiKey)
                }

                Install-Module_BlockScope -script {
                    Publish-Module @additionalParams -Path $modulePath -Repository $repository -Force
                }.GetNewClosure()

            }
        }
        
        $repository = $repositoryList[0]
        Uninstall-Module -Name $moduleName -ErrorAction SilentlyContinue
        
        # Install-Module -Name $moduleName -Scope CurrentUser -Repository $repository `
        # -Force -AllowClobber -RequiredVersion $psd.ModuleVersion
    }
}

function Publish-ModuleEx:::Example {
    $folderModules = Get-Modulefolders -path C:\code\PowerFrame 
    $folderModules.FullName | Initialize-Module_Manifest_EnsureFiles
    $folderModules.FullName | Publish-ModuleEx 
}

function Get-ScriptCommandDependencies {
    # based on https://mikefrobbins.com/2019/02/21/powershell-tokenizer-more-accurate-than-ast-in-certain-scenarios/
    # see also https://mikefrobbins.com/2019/05/17/using-the-ast-to-find-module-dependencies-in-powershell-functions-and-scripts/
    param (
        [Parameter(ValueFromPipeline=$true)]
        $file,
        [Switch]$FailOnMissingCommand,
        [Switch]$IgnoreSameModule
    )
    begin {
        $getCommandErrorAction =  ($FailOnMissingCommand) ? 'Stop' : 'SilentlyContinue' 
    }
    process {
        $file = $file | Get-Path
        $Token = $null
        $ignoredOutput = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$Token, [ref]$null)
        Write-Verbose $ignoredOutput
        $commandTokenList =  $Token | Where-Object {$_.TokenFlags -eq 'CommandName'}
        $commandNameList = $commandTokenList.Value.ToLowerInvariant() 
            | Select-Object -Unique | Sort-Object
        $commandList = $commandNameList | ForEach-Object {
            Get-Command -name $_ -ErrorAction $getCommandErrorAction
        }
        $commandList = $commandList | Where-Object { $_.Module.Path }

        if ($IgnoreSameModule) {
            $commandList = $commandList | Where-Object { 
                $cmdPath = $_.Module.Path
                $moduleFolder = split-path -Path  $cmdPath -Parent 
                -not (Test-Path_Parent -path $file -parent $moduleFolder)
            }
        }
        $commandList
    }
}
function Get-ScriptCommandDependencies:::Examples{
    $result = Get-ChildItem -Path .\pf-AzPipelines -filter *.ps1 -Recurse
        | Get-ScriptCommandDependencies -IgnoreSameModule
    $result
}

function Get-ModuleDependencies {
    param (
        [Parameter(ValueFromPipeline=$true)]
        $file,
        [Switch]$FailOnMissingCommand 
    )
    process {
        $commandList = $file |  Get-ScriptCommandDependencies -IgnoreSameModule `
            -FailOnMissingCommand:($FailOnMissingCommand.IsPresent)
        $commandList.Module
    }
}
function Get-ModuleDependencies:::Example {
    $result = Get-ChildItem -Path .\pf-AzPipelines -filter *.ps1 -Recurse | 
        Get-ModuleDependencies
    $result
}

function Get-ScriptFunctions {
    param (
        [Parameter(ValueFromPipeline=$true)]
        $file
    )
    process {
        $file = $file.fullname ?? $file
        $Token = $null
        $ignoredOutput = [System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$Token, [ref]$null)
        Write-Verbose $ignoredOutput
        $funcBegin = $false 
        $Token | ForEach-Object {
            if ($funcBegin) {
                $_.Text
            }
            $funcBegin = ($_.Kind -like "function") 
        }
    }
}
function Get-ScriptFunctions:::Example {
    Get-ChildItem pf-* -Recurse -Filter *.ps1 | Select-Object -First 1 | Get-ScriptFunctions
}