PSHelper.psm1

#region Private Functions
function Get-PSScriptContent
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        #ScriptBlock
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [System.Management.Automation.Language.ScriptBlockAst]$ScriptBlock
    )
    
    Begin
    {
          
    }

    Process
    {
        $SB = New-Object -TypeName System.Text.StringBuilder -ErrorAction Stop

        #Add ParamBlcok
        if (-not [String]::IsNullOrEmpty($ScriptBlock.ParamBlock))
        {
            $null = $SB.AppendLine($ScriptBlock.ParamBlock.ToString())
        }

        #Add DynamicParamBlock
        if (-not [String]::IsNullOrEmpty($ScriptBlock.DynamicParamBlock))
        {
            $null = $SB.AppendLine($ScriptBlock.DynamicParamBlock.ToString())
        }

        #Add BeginBlock
        if (-not [String]::IsNullOrEmpty($ScriptBlock.BeginBlock))
        {
            $null = $SB.AppendLine($ScriptBlock.BeginBlock.ToString())
        }

        #Add ProcessBlock
        if (-not [String]::IsNullOrEmpty($ScriptBlock.ProcessBlock))
        {
            $null = $SB.AppendLine($ScriptBlock.ProcessBlock.ToString())
        }

        #Add EndBlock
        if (-not [String]::IsNullOrEmpty($ScriptBlock.EndBlock))
        {
            $null = $SB.AppendLine($ScriptBlock.EndBlock.ToString())
        }

        $SB.ToString()
    }

    End
    {

    }
}
#endregion

#region Public Functions
function Add-PSModulePathEntry
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #Path
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [string[]]$Path,

        #Force
        [Parameter(Mandatory=$false,ParameterSetName='NoRemoting_Default')]
        [switch]$Force = $false,

        #Scope
        [Parameter(Mandatory=$false,ParameterSetName='NoRemoting_Default')]
        [System.EnvironmentVariableTarget[]]$Scope = 'Machine'
    )
   
    Process
    {
        $Scope | foreach {

            #Get Current Entries
            $CurrentEntries = Get-PSModulePath -Scope $_
            $CurPSModulePathArr = New-Object -TypeName System.Collections.ArrayList
            foreach ($Entry in $CurrentEntries)
            {
                if (-not [string]::IsNullOrEmpty($Entry))
                {
                    $null = $CurPSModulePathArr.Add($Entry)
                }
            }

            #Add Entries
            foreach ($Item in $Path)
            {
                if ($CurPSModulePathArr -notcontains $Item)
                {
                    if ((Test-Path $Item) -or ($Force.IsPresent))
                    {
                        $null = $CurPSModulePathArr.Add($Item)
                    }
                    else
                    {
                        Write-Error -Message "Path: $Item does not exits" -ErrorAction Stop
                    }
                }
            }

            [System.Environment]::SetEnvironmentVariable('PsModulePath',($CurPsModulePathArr -join ';'),[System.EnvironmentVariableTarget]::$_)
        }
    }
}

function Set-PSModulePath
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #Path
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [string[]]$Path,

        #Force
        [Parameter(Mandatory=$false,ParameterSetName='NoRemoting_Default')]
        [switch]$Force = $false,

        #Scope
        [Parameter(Mandatory=$false,ParameterSetName='NoRemoting_Default')]
        [System.EnvironmentVariableTarget[]]$Scope = 'Machine'
    )
    
    Begin
    {
          
    }

    Process
    {
        $Scope | foreach {
            $CurPsModulePathArr = New-Object System.Collections.ArrayList
            foreach ($Item in $Path)
            {
                if ((Test-Path $Item) -or ($Force.IsPresent))
                {
                    $CurPsModulePathArr += $Item
                }
                else
                {
                    Write-Error -Message "Path: $Item does not exits" -ErrorAction Stop
                }

            }
            [System.Environment]::SetEnvironmentVariable('PsModulePath',($CurPsModulePathArr -join ';'),$_)
        }
   }

    End
    {

    }
}

function Get-PSModulePath
{
    [CmdletBinding()]
    [OutputType([string[]])]
    param
    (
        #Scope
        [Parameter(Mandatory=$false,ParameterSetName='NoRemoting_Default')]
        [System.EnvironmentVariableTarget[]]$Scope = 'Machine'
    )
    
    Process
    {
        $Scope | foreach { [System.Environment]::GetEnvironmentVariable('PsModulePath',$_) -split ';' }
    }
}

function Remove-PSModulePathEntry
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #Path
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [string[]]$Path,

        #Scope
        [Parameter(Mandatory=$false,ParameterSetName='NoRemoting_Default')]
        [System.EnvironmentVariableTarget[]]$Scope = 'Machine'
    )

    Process
    {
        $Scope | foreach {

            #Get Current Entries
            $CurrentEntries = Get-PSModulePath -Scope $_
            $CurPSModulePathArr = New-Object -TypeName System.Collections.ArrayList
            foreach ($Entry in $CurrentEntries)
            {
                if (-not [string]::IsNullOrEmpty($Entry))
                {
                    $null = $CurPSModulePathArr.Add($Entry)
                }
            }

            #Remove Entries
            foreach ($Item in $Path)
            {
                if ($CurPsModulePathArr -contains $Item)
                {
                    $CurPsModulePathArr.Remove($Item)
                }
                else
                {
                    Write-Warning "PSModulePath does not contains: $Item"
                }
            }

            [System.Environment]::SetEnvironmentVariable('PsModulePath',($CurPsModulePathArr -join ';'),$_)
        }
    }
}

function Test-PSModule
{
    [CmdletBinding()]
    [OutputType([PSModuleValidation])]
    param
    (
        #ModulePath
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [System.IO.DirectoryInfo[]]$ModulePath
    )
    
    Process
    {
        foreach ($Item in $ModulePath)
        {
            $ModuleValidation = [PSModuleValidation]::new()

            #Resolve Module Definition File
            try
            {
                Write-Verbose "Resolve Module Definition File started"
            
                $ModuleDefinitionFileName  = $Item.Name + '.psd1'
                $ModuleDefinitionFile = Get-ChildItem -Path $item.FullName -Recurse -filter $ModuleDefinitionFileName -ErrorAction Stop -File
                if (-not $ModuleDefinitionFile)
                {
                    throw "$ModuleDefinitionFileName not found"
                }
                Write-Verbose "Resolve Module Definition File completed"
            }
            catch
            {
                Write-Error "Resolve Module Definition File failed. Details: $_" -ErrorAction 'Stop'
            }


            #Validate Module
            try
            {
                Write-Verbose "Validate Module started"
                $ModuleInfo = Get-Module -ListAvailable -FullyQualifiedName $ModuleDefinitionFile.FullName -Refresh -ErrorAction Stop | sort -Property Version | select -Last 1
                if ($ModuleInfo)
                {
                    $ModuleValidation.IsModule=$true
                    $ModuleValidation.ModuleInfo = $ModuleInfo
                }
      
                Write-Verbose "Validate Module completed"
            }
            catch
            {
            }


            #Validate Version Integrity
            if ($ModuleValidation.IsModule)
            {
                #Check Version Control
                try
                {
                    $ModulePsd = Import-PSDataFile -FilePath $ModuleValidation.ModuleInfo.Path -ErrorAction Stop
                    $VersionControl = $ModulePsd.PrivateData.VersionControl | ConvertFrom-Json -ErrorAction Stop
                }
                catch
                {

                }

                if ($VersionControl)
                {
                    $ModuleValidation.SupportVersonControl = $true
                    $GetFileHash_Params = @{
                        Path=(Join-Path -Path $ModuleValidation.ModuleInfo.ModuleBase -ChildPath $ModuleValidation.ModuleInfo.RootModule -ErrorAction Stop)
                    }
                    if ($VersionControl.HashAlgorithm)
                    {
                        $GetFileHash_Params.Add('Algorithm',$VersionControl.HashAlgorithm)
                    }
                    $CurrentHash = Get-FileHash @GetFileHash_Params -ErrorAction Stop

                    if ($VersionControl.Version -eq $ModuleValidation.ModuleInfo.Version)
                    {
                        if ($VersionControl.Hash -eq $CurrentHash.Hash)
                        {
                            $ModuleValidation.IsVersionValid=$true
                        }
                        else
                        {
                            $ModuleValidation.IsNewVersion = $true
                        }
                    }
                }
            }

            #Validate IsReadyForPackaging
            if ($ModuleValidation.IsModule)
            {
                if ($ModuleValidation.ModuleInfo.Author -and $ModuleValidation.ModuleInfo.Description)
                {
                    $ModuleValidation.IsReadyForPackaging = $true
                }
            }

            $ModuleValidation
        }
    }
}

function Test-PSScript
{
    [CmdletBinding()]
    [OutputType([PSModuleValidation])]
    param
    (
        #ModulePath
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [System.IO.FileInfo[]]$ScriptPath
    )

    Process
    {
        foreach ($Item in $ScriptPath)
        {
            $ScriptValidation = [PSScriptValidation]::new()

            #Validate Script
            try
            {
                Write-Verbose "Validate Script started"
                $ScriptInfo = Test-ScriptFileInfo -Path $Item.FullName -ErrorAction Stop
                if ($ScriptInfo)
                {
                    $ScriptValidation.IsScript=$true
                    $ScriptValidation.ScriptInfo = $ScriptInfo
                }
      
                Write-Verbose "Validate Script completed"
            }
            catch
            {
                if ($_.FullyQualifiedErrorId -ilike '*ScriptParseError*')
                {
                    foreach ($item in $_.TargetObject)
                    {
                        $ScriptValidation.ValidationErrors += @"
At $($item.Extent.File) line:$($item.Extent.StartLineNumber) expr:$($item.Extent)
+ [$($item.ErrorId)] $($item.Message)
"@

                    }
                }
            }

            #Validate Version Integrity
            if ($ScriptValidation.IsScript)
            {
                #Check Version Control
                try
                {
                    $VersionControl = $ScriptValidation.ScriptInfo.PrivateData | ConvertFrom-Json -ErrorAction Stop | Select-Object -ExpandProperty VersionControl
                }
                catch
                {

                }

                if ($VersionControl)
                {
                    $ScriptValidation.SupportVersonControl = $true

                    if ($VersionControl.Version -eq $ScriptValidation.ScriptInfo.Version)
                    {
                        #Calculate scriptContent hash
                        $ScriptContent_Raw = Get-Command -Name $ScriptValidation.ScriptInfo.Path -ErrorAction Stop
                        $ScriptContent = Get-PSScriptContent -ScriptBlock $ScriptContent_Raw.ScriptBlock.Ast -ErrorAction Stop
                        $ScriptContent_BA = [System.Text.Encoding]::UTF8.GetBytes($ScriptContent)
                        $ScriptContent_MemoryStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList @(,$ScriptContent_BA)
                        $GetFileHash_Params = @{
                            InputStream=$ScriptContent_MemoryStream
                        }
                        if ($VersionControl.HashAlgorithm)
                        {
                            $GetFileHash_Params.Add('Algorithm',$VersionControl.HashAlgorithm)
                        }
                        $CurrentHash = Get-FileHash @GetFileHash_Params -ErrorAction Stop
                        $ScriptContent_MemoryStream.Dispose()
                        if ($VersionControl.Hash -eq $CurrentHash.Hash)
                        {
                            $ScriptValidation.IsVersionValid=$true
                        }
                        else
                        {
                            $ScriptValidation.IsNewVersion = $true
                        }
                    }
                }
            }

            #Validate IsReadyForPackaging
            if ($ScriptValidation.IsScript)
            {
                if ($ScriptValidation.ScriptInfo.Author -and $ScriptValidation.ScriptInfo.Description)
                {
                    $ScriptValidation.IsReadyForPackaging = $true
                }
            }

            $ScriptValidation
        }
    }
}

function Update-PSModuleVersion
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #ModulePath
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [System.IO.DirectoryInfo[]]$ModulePath
    )
    
    Begin
    {
          
    }

    Process
    {
        $ModuleValidation = Test-PSModule -ModulePath $ModulePath -ErrorAction Stop
        if ($ModuleValidation.IsModule)
        {
            $ModuleInfo = $ModuleValidation.ModuleInfo
            $CurrentVersion = Get-Version -InputObject $ModuleInfo.Version
            $NewVersion = [System.Version]::new($CurrentVersion.Major,$CurrentVersion.Minor,$CurrentVersion.Build,($CurrentVersion.Revision + 1))
            $NewHash = Get-FileHash -Path (Join-Path -Path $ModuleValidation.ModuleInfo.ModuleBase -ChildPath $ModuleValidation.ModuleInfo.RootModule -ErrorAction Stop) -ErrorAction Stop
            $VersionControlAsJson = ConvertTo-Json -InputObject ([pscustomobject]@{
                Hash=$NewHash.Hash
                HashAlgorithm=$NewHash.Algorithm
                Version=$NewVersion.ToString()
            }) -ErrorAction Stop -Compress
            Update-ModuleManifest -Path $ModuleValidation.ModuleInfo.Path -ModuleVersion $NewVersion -PrivateData @{
                VersionControl=$VersionControlAsJson
            } -ErrorAction Stop
        }
    }

    End
    {

    }
}

function Update-PSScriptVersion
{
    [CmdletBinding()]
    [OutputType([void])]
    param
    (
        #ModulePath
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [System.IO.FileInfo[]]$ScriptPath
    )
    
    Begin
    {
          
    }

    Process
    {
        foreach ($item in $ScriptPath)
        {
            $ScriptValidation = Test-PSScript -ScriptPath $item -ErrorAction Stop
            if ($ScriptValidation.IsScript)
            {
                $ScriptInfo = $ScriptValidation.ScriptInfo
                $CurrentVersion = [System.Version]::Parse($ScriptInfo.Version)
                $NewVersion = [System.Version]::new($CurrentVersion.Major,$CurrentVersion.Minor,$CurrentVersion.Build,($CurrentVersion.Revision + 1))

                #Calculate scriptContent hash
                $ScriptContent_Raw = Get-Command -Name $ScriptInfo.Path -ErrorAction Stop
                $ScriptContent = Get-PSScriptContent -ScriptBlock $ScriptContent_Raw.ScriptBlock.Ast -ErrorAction Stop
                $ScriptContent_BA = [System.Text.Encoding]::UTF8.GetBytes($ScriptContent)
                $ScriptContent_MemoryStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList @(,$ScriptContent_BA)
                $NewHash = Get-FileHash -InputStream $ScriptContent_MemoryStream -ErrorAction Stop
                $ScriptContent_MemoryStream.Dispose()
                $VersionControlAsJson = ConvertTo-Json -InputObject ([pscustomobject]@{
                    VersionControl=[pscustomobject]@{
                        Hash=$NewHash.Hash
                        HashAlgorithm=$NewHash.Algorithm
                        Version=$NewVersion.ToString()
                    }
                }) -ErrorAction Stop -Compress
                Update-ScriptFileInfo -Path $ScriptInfo.path -PrivateData $VersionControlAsJson -Version $NewVersion -ErrorAction Stop
            }
        }
    }

    End
    {

    }
}
#endregion

#region Public Classes

class PSModuleValidation {

    [psmoduleinfo]$ModuleInfo
    [bool]$IsModule=$false
    [bool]$IsVersionValid=$false
    [bool]$IsNewVersion=$false
    [bool]$SupportVersonControl=$false
    [bool]$IsReadyForPackaging=$false
    hidden [bool]$_test = (Add-Member -InputObject $this -MemberType ScriptProperty -Name IsValid -Value {
        if ($this.IsModule -and $this.IsVersionValid -and $this.SupportVersonControl)
        {
            $true
        }
        else
        {
            $false
        }
    } -SecondValue {})

}

class PSScriptValidation {

    [PSCustomObject]$ScriptInfo
    [bool]$IsScript=$false
    [bool]$IsVersionValid=$false
    [bool]$IsNewVersion=$false
    [bool]$SupportVersonControl=$false
    [bool]$IsReadyForPackaging=$false
    [string[]]$ValidationErrors=''
    hidden [bool]$_test = (Add-Member -InputObject $this -MemberType ScriptProperty -Name IsValid -Value {
        if ($this.IsScript -and $this.IsVersionValid -and $this.SupportVersonControl)
        {
            $true
        }
        else
        {
            $false
        }
    } -SecondValue {})

}
#endregion