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 { $VerbosePreference = 'SilentlyContinue' $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 { $VerbosePreference = 'SilentlyContinue' $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 { $VerbosePreference = 'SilentlyContinue' $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 { $VerbosePreference = 'SilentlyContinue' $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)] [System.IO.DirectoryInfo[]]$ModulePath, #Version [Parameter(Mandatory = $false)] [Version]$Version ) Begin { $oldVerbosePref = $VerbosePreference $VerbosePreference = 'SilentlyContinue' } Process { foreach ($Item in $ModulePath) { $ModuleValidation = [PSModuleValidation]::new() try { #Resolve Module Definition File Write-Information -MessageData "[$($item.Name)] Validating Module" $ModuleDefinitionFileName = $Item.Name + '.psd1' $ModuleDefinitionFile = Get-ChildItem -Path $item.FullName -Recurse -filter $ModuleDefinitionFileName -ErrorAction Stop -File if($ModuleDefinitionFile) { Write-Debug -Message "[$($ModuleDefinitionFile.Name)] Resolving ModuleInfo" $ModuleInfoAllVersions = Get-Module -ListAvailable -FullyQualifiedName $ModuleDefinitionFile.FullName -Refresh -ErrorAction Stop -Verbose:$false if($PSBoundParameters.ContainsKey("Version")) { $ModuleInfo = $ModuleInfoAllVersions | Where-Object { $_.Version -eq $Version } } else { $ModuleInfo = $ModuleInfoAllVersions | Sort-Object -Property Version | Select-Object -Last 1 } if ($ModuleInfo) { $ModuleValidation.IsModule = $true $ModuleValidation.ModuleInfo = $ModuleInfo $ModuleValidation.IsReadyForPackaging = ((-not [String]::IsNullOrWhiteSpace($ModuleValidation.ModuleInfo.Author)) -and (-not [String]::IsNullOrWhiteSpace($ModuleValidation.ModuleInfo.Description))) Write-Debug -Message "[$($ModuleInfo.Name)] Analyzing Version Control" $ModulePsd = $null $VersionControl = $null $CurrentHash = $null $ModulePsd = Import-PSDataFile -FilePath $ModuleValidation.ModuleInfo.Path -ErrorAction SilentlyContinue $VersionControl = $ModulePsd.PrivateData.VersionControl | ConvertFrom-Json -ErrorAction SilentlyContinue if ($VersionControl) { $ModuleValidation.SupportVersonControl = $true $GetFileHash_Params = @{ Path = ([IO.Path]::Combine(($ModuleValidation.ModuleInfo.ModuleBase), ($ModuleValidation.ModuleInfo.RootModule))) } if ($VersionControl.HashAlgorithm) { $GetFileHash_Params.Add('Algorithm', $VersionControl.HashAlgorithm) } $CurrentHash = Get-FileHash @GetFileHash_Params if ($VersionControl.Version -eq $ModuleValidation.ModuleInfo.Version) { if ($VersionControl.Hash -eq $CurrentHash.Hash) { $ModuleValidation.IsVersionValid = $true } else { $ModuleValidation.IsNewVersion = $true } } } Write-Debug -Message "[$($ModuleInfo.Name)] Analysis completed" } } } catch { # Test functions should not throw exceptions. If something is wrong with the module, it will be indicated in one of the bool flags of the result } $ModuleValidation } } End { $VerbosePreference = $oldVerbosePref } } function Test-PSScript { [CmdletBinding()] [OutputType([PSScriptValidation])] param ( #ScriptPath [Parameter(Mandatory = $true)] [System.IO.FileInfo[]]$ScriptPath, #UseScriptConfigFile [Parameter(Mandatory = $false)] [switch]$UseScriptConfigFile ) Process { foreach ($Item in $ScriptPath) { $ScriptValidation = [PSScriptValidation]::new() #Get ScriptConfig if ($UseScriptConfigFile.IsPresent) { $ScriptValidation.ScriptConfig = [PSScriptConfig]::new() $ScriptConfigFilePath = Join-Path -Path $Item.DirectoryName -ChildPath "$($Item.BaseName).config.json" if (Test-Path -Path $ScriptConfigFilePath) { $ScriptConfig = Get-Content -Path $ScriptConfigFilePath -raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop #Parse RequiredModules if ($ScriptConfig.RequiredModules) { $ScriptConfig.RequiredModules | foreach { $ScriptValidation.ScriptConfig.RequiredModules.add($_) } } } } #Validate Script try { Write-Verbose "[Validate Script] Processing '$($Item.FullName)'" $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 -Verbose:$false $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 { $VerbosePreference = 'SilentlyContinue' } 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 -Verbose:$false } } 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 { } } function Update-PSScriptConfig { [CmdletBinding()] param ( #ScriptPath [Parameter(Mandatory = $true)] [System.IO.FileInfo]$ScriptPath, #ScriptConfig [Parameter(Mandatory = $true)] [PSScriptConfig]$ScriptConfig ) process { $ScriptConfigFilePath = Join-Path -Path $ScriptPath.DirectoryName -ChildPath "$($ScriptPath.BaseName).config.json" out-File -FilePath $ScriptConfigFilePath -InputObject ($ScriptConfig | ConvertTo-Json -ErrorAction Stop) -Force } } #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 PSScriptConfig { [System.Collections.Generic.List[psobject]]$RequiredModules = ([System.Collections.Generic.List[psobject]]::new()) } class PSScriptValidation { [PSCustomObject]$ScriptInfo [PSScriptConfig]$ScriptConfig = $Null [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 |