EditModule.psm1

#Requires -Modules PSLogger
Function Edit-Module 
{
<#
    .SYNOPSIS
        Opens a specified PowerShell module, for editing, in the ISE
    .DESCRIPTION
        This function uses the Get-Module cmdlet to search for and retrieve information about a module (expected / available in $env:PSModulePath ) and then
        it opens the module from that location into a new tab in ISE for editing. Wildcard characters that resolve to a single module are supported. This function always opens the manifest file to be edited, and prompts/encourages the user/editor to update the ModuleVersion. Additional Module files such as the RootModule / module script (.psm1), and
 
        PowerShell Module properties changed in PowerShell v3, and so the behavior of the original Edit-Module function (from Scripting Guy Ed Wilson's 'PowerShellISEModule') also changed. The following updates are intended to enable easy editing of both the Data (Manifest) file as well extending similar ease of use for editing the Module script (.psm1), and other scripts included in a Module.
 
        If the Module is installed into a shared file system path (e.g. $env:ProgramFiles), Edit-Module will attempt to open the ISE with elevated permissions, which are necessary to edit a Module in place. If the user/editor cannot gain elevated permissions, then the ISE will open the module file(s) with read-only rights.
    .EXAMPLE
        Edit-Module PowerShellISEModule
        Edit-Module PowerShellISEModule opens the PowerShellISEModule into a new tab in the ISE for editing
    .EXAMPLE
        Edit-Module PowerShellISE*
        Edit-Module PowerShellISE* opens the PowerShellISEModule into a new tab in the ISE for editing by using a wild card character for the module name
    .PARAMETER NAME
        The name of the module. Wild cards that resolve to a single module are supported.
    .NOTES
        NAME: Edit-Module
        AUTHOR: originally authored "by ed wilson, msft"
            Edited by Bryan Dady (@bcdady)to extend PowerShell v3 functionality.
            Enhancements include Param support, a new FileType parameter, support to edit modules imported into the active session as well as from -ListAvailable.
            Also adds ability to search for a Module by function name, and opening files in an elevated ISE session if/as necessary.
        LASTEDIT: 04/29/2015
        KEYWORDS: Scripting Techniques, Modules
 
    .LINK
        http://www.ScriptingGuys.com
        PSLogger
#>

    [cmdletbinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Specify a module to edit'
        )]
        [ValidateScript({$PSItem -in (Get-Module -ListAvailable).Name})]
        [string]
        $Name,

        [Parameter(
            Mandatory = $false,
            Position = 1,
            HelpMessage = 'Specify Script Module (.psm1), or manifest / Script Data file (.psd1) <optional>'
        )]
        [ValidateSet('Manifest','ScriptModule','Scripts','All')]
        [string]
        $FileType = 'ScriptModule'
    )

    # Get an object for the module, for easier access in later steps
    $ModuleObj = (Get-Module -ListAvailable -Name $Name)

    # Test if we got a valid Script Module object; RootModule should point to the .psm1 file
    if ($ModuleObj.RootModule -ne $null) 
    {
        Write-Log -Message "Preparing to edit module ($ModuleObj.Name) (from ($ModuleObj.ModuleBase))in PowerShell ISE" -Function EditModule
        # Now that we've got a valid module object to work with, we can pick the files we want to open in ISE
        # Get the Module Type - "such as a script file or an assembly. This property is introduced in Windows PowerShell 2.0".
        # https://msdn.microsoft.com/en-us/library/system.management.automation.psmoduleinfo.moduletype(v=vs.85).aspx
        if ($ModuleObj.ModuleType -eq 'Script') 
        {
            # .Path property points to the .psd1 module manifest file :: https://msdn.microsoft.com/en-us/library/dd878324%28v=vs.85%29.aspx
            if (Test-Path -Path $ModuleObj.Path -PathType Leaf -ErrorAction SilentlyContinue ) 
            {
                if (($ModuleObj.Path) | Select-String -Pattern $env:ProgramFiles -SimpleMatch) 
                {
                    # Path to module is Program Files, so editing the module file(s) requires elevated privileges
                    [bool]$SharedModule = $true
                }
                # This function always opens the manifest to be edited, and prompts/encourages the user/editor to update the ModuleVersion.
    
                Write-Debug -Message "Module Manifest Path: ($ModuleObj.Path)"
                Write-Output -InputObject "`nOpening Module Manifest $($ModuleObj.Path), Version: $($ModuleObj.Version.ToString())`n`n`tPlease update the Version and Help comments to reflect any changes made."
                Start-Sleep -Seconds 2
            }
        }
        else 
        {
            Write-Log -Message "Unexpected ModuleType is $($ModuleObj.ModuleType)" -Function  -Function EditModule -Verbose
            throw "Unexpected ModuleType is $($ModuleObj.ModuleType)"
        }
 
        Write-Log -Message "Loading FileType(s): $FileType for editing" -Function EditModule

        switch ($FileType) {
            'ScriptModule'
            {
                Write-Log -Message "Editing Module Root Script: $($ModuleObj.RootModule)" -Function EditModule
                if ($SharedModule) 
                {
                    Open-AdminISE -File $ModuleObj.Path
                    Open-AdminISE -File $(Join-Path -Path $ModuleObj.ModuleBase -ChildPath $ModuleObj.RootModule)
                }
                else 
                {
                    & powershell_ise.exe -File $ModuleObj.Path
                    & powershell_ise.exe -File $(Join-Path -Path $ModuleObj.ModuleBase -ChildPath $ModuleObj.RootModule)
                }
            }
            'Scripts' 
            {
                $ModuleObj.NestedModules | ForEach-Object -Process {
                    Write-Log -Message "Editing Nested Module: $PSItem" -Function EditModule
                    # ($ModuleObj.NestedModules | Get-ChildItem).FullName
                    if ($SharedModule) 
                    {
                        Open-AdminISE -File ($PSItem | Get-ChildItem).FullName
                    }
                    else 
                    {
                        & powershell_ise.exe -File ($PSItem | Get-ChildItem).FullName
                    }
                }
                $ModuleObj.scripts | ForEach-Object -Process {
                    Write-Log -Message "Editing script: $PSItem" -Function EditModule
                    if ($SharedModule) 
                    {
                        Open-AdminISE -File  $PSItem.Path
                    }
                    else 
                    {
                        & powershell_ise.exe -File $PSItem.Path
                    }
                }
            }
            'All' 
            {
                Write-Log -Message "Editing all module files and scripts for $ModuleObj" -Function EditModule
                if ($SharedModule) 
                {
                    Open-AdminISE -File $ModuleObj.Path
                    Open-AdminISE -File $(Join-Path -Path $ModuleObj.ModuleBase -ChildPath $ModuleObj.RootModule)
                    $ModuleObj.NestedModules | ForEach-Object -Process {
                        Open-AdminISE -File $PSItem.Path 
                    }
                    $ModuleObj.scripts | ForEach-Object -Process {
                        Open-AdminISE -File $PSItem.Path 
                    }
                }
                else 
                { 
                    & powershell_ise.exe -File $ModuleObj.Path
                    & powershell_ise.exe -File $(Join-Path -Path $ModuleObj.ModuleBase -ChildPath $ModuleObj.RootModule)
                    $ModuleObj.NestedModules | ForEach-Object -Process {
                        & powershell_ise.exe -File $PSItem.Path 
                    }
                    $ModuleObj.scripts | ForEach-Object -Process {
                        & powershell_ise.exe -File $PSItem.Path 
                    }
                }
            }
            default
            {
                Write-Log -Message "Editing Module Manifest: $($ModuleObj.Path)" -Function EditModule
                if ($SharedModule) 
                {
                    Open-AdminISE -File $ModuleObj.Path
                }
                else 
                {
                    & powershell_ise.exe -File $ModuleObj.Path
                }            }
        } # end switch block
    }
    else 
    {
        Write-Log -Message 'Failed to locate path(s) to module files for editing.' -Function EditModule -Verbose
    } # end if ; no matching module found

} #end function Edit-Module

function Open-AdminISE {
<#
    .SYNOPSIS
        Launch a new PowerShell ISE window, with elevated privileges
    .DESCRIPTION
        Simplifies opening a PowerShell ISE editor instance, with Administrative permissions, from the console / keyboard, instead of having to grab the mouse to Right-Click and select 'Run as Administrator'
#>

    [cmdletbinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Specify a module name to edit'
        )]
        [ValidateScript({Resolve-Path -Path $PSItem})]
        [Alias('File','FilePath','Module')]
        [string]
        $Path
    )

    Write-Log -Message 'Opening ""$Path""" in ISE, with elevated priveleges.' -Function EditModule -Verbose
    Start-Process -FilePath "$PSHOME\powershell_ise.exe" -ArgumentList "-File ""$Path""" -Verb RunAs -WindowStyle Normal
}

New-Alias -Name Open-AdminEditor -Value Open-AdminISE -ErrorAction Ignore

function Find-Function 
{
<#
    .SYNOPSIS
        Returns Module details, to which a specified function belongs.
    .DESCRIPTION
        Uses Get-Module and Select-String to find the RootModule which provides a specified ExportedCommand / Function name.
    .EXAMPLE
        PS C:\> Find-Function -SearchPattern 'Edit*'
 
        ModuleName : Edit-Module
        FunctionName : EditModule
    .NOTES
        NAME : Find-Function
        VERSION : 1.0.1
        LAST UPDATED: 6/25/2015
        AUTHOR : Bryan Dady
    .INPUTS
        None
    .OUTPUTS
        Write-Log
#>

        Param (

        [Parameter(Mandatory = $true, Position = 0)]
        [String]
        $SearchPattern,

        # Use SimpleMatch (non RegEx) behavior in Select-String
        [Parameter(Mandatory = $false, Position = 1)]
        [switch]
        $SimpleMatch = $false

    )

    New-Variable -Name OutputObj -Description 'Object to be returned by this function' -Scope Private
    Get-Module -ListAvailable |
    Select-Object -Property Name, ExportedCommands | 
    ForEach-Object -Process {
        # find and return only Module/function details where the pattern is matched in the name of the function
        if ($PSItem.ExportedCommands.Keys |
        Select-String -Pattern $SearchPattern) 
        {
            # Optimize New-Object invocation, based on Don Jones' recommendation: https://technet.microsoft.com/en-us/magazine/hh750381.aspx

            $Private:properties = @{
                'ModuleName' = $PSItem.Name
                'FunctionName' = $PSItem.ExportedCommands
            }
            $Private:RetObject = New-Object -TypeName PSObject -Property $properties

            return $RetObject

        } # end if

    } # end of foreach

} # end function Find-Function