Private/cliHelper.env.Utils/cliHelper.env.Utils.psm1
#!/usr/bin/env pwsh using namespace System.IO enum ctxOption { Remove = 0 Add = 1 None = 2 } enum dtActn { Assign Prefix Suffix } #region classes class dotEntry { [ValidateNotNullOrWhiteSpace()][string]$Name [string]$Value hidden [dtActn]$Action dotEntry($n, $v, $a) { $this.Name = $n; $this.Action = $a; $this.Value = $v } [void] Set([string]$Name, [string]$value) { $this.Name = $Name; $this.Value = $value } [string] ToString() { $__str = '{0}{1}{2}' -f $this.Name, @{ Assign = '='; Prefix = ":="; Suffix = "=:" }["$($this.Action)"], $this.Value return $__str } } class EnvCfg { [bool]$autoSync = $false EnvCfg() {} EnvCfg([hashtable[]]$items) { $this.Add($items) } [void] Add([string]$key, [System.Object]$value) { [ValidateNotNullOrEmpty()][string]$key = $key if (!$this.Contains($key)) { $htab = [hashtable]::new(); $htab.Add($key, $value); $this.Add($htab) } else { Write-Warning "Cfg.Add() Skipped $Key. Key already exists." } } [void] Add([hashtable]$item) { [ValidateNotNullOrEmpty()][hashtable]$item = $item $Keys = $item.Keys | Where-Object { !$this.Contains($_) -and ($_.GetType().FullName -eq 'System.String' -or $_.GetType().BaseType.FullName -eq 'System.ValueType') } foreach ($key in $Keys) { $this | Add-Member -MemberType NoteProperty -Name $key -Value $item[$key] } } [void] Add([hashtable[]]$items) { foreach ($item in $items) { $this.Add($item) } } [void] Add([System.Collections.Generic.List[hashtable]]$items) { foreach ($item in $items) { $this.Add($item) } } [void] Remove([string[]]$keys) { $keys.ForEach({ $this.PsObject.Properties.Remove($_) }) } [void] Set([hashtable]$item) { $Keys = $item.Keys | Sort-Object -Unique foreach ($key in $Keys) { $value = $item[$key] [ValidateNotNullOrEmpty()][string]$key = $key [ValidateNotNullOrEmpty()][System.Object]$value = $value if ($this.psObject.Properties.Name.Contains([string]$key)) { $this."$key" = $value } else { $this.Add($key, $value) } } } [void] Set([string]$key, [System.Object]$value) { [ValidateNotNullOrEmpty()][string]$key = $key [AllowNull()][System.Object]$value = $value if ($this.psObject.Properties.Name.Contains([string]$key)) { $this."$key" = $value } else { $this.Add($key, $value) } } [void] Set([System.Collections.Specialized.OrderedDictionary]$dict) { $dict.Keys.Foreach({ $this.Set($_, $dict["$_"]) }); } [bool] Contains([string]$Name) { [ValidateNotNullOrEmpty()][string]$Name = $Name return (($this | Get-Member -Type NoteProperty | Select-Object -ExpandProperty name) -contains "$Name") } [array] ToArray() { $array = @(); $props = $this | Get-Member -MemberType NoteProperty if ($null -eq $props) { return @() } $props.name | ForEach-Object { $array += @{ $_ = $this.$_ } } return $array } [PSCustomObject] ToPsObject() { return ($this.ToJson() | ConvertFrom-Json) } [string] ToJson() { return [string]($this | Select-Object * | ConvertTo-Json) } [System.Collections.Specialized.OrderedDictionary] ToOrdered() { $dict = [System.Collections.Specialized.OrderedDictionary]::new(); $Keys = $this.PsObject.Properties.Where({ $_.Membertype -like "*Property" }).Name if ($Keys.Count -gt 0) { $Keys | ForEach-Object { [void]$dict.Add($_, $this."$_") } } return $dict } [void] Import([string]$FilePath) { $this.Import($FilePath, [System.Text.Encoding]::UTF8) } [void] Import([string]$FilePath, [System.Text.Encoding]$Encoding) { [ValidateNotNullOrEmpty()][string]$FilePath = $FilePath [ValidateNotNullOrEmpty()][System.Text.Encoding]$Encoding = $Encoding $this.Import($(ConvertFrom-Json -InputObject $([IO.File]::ReadAllText($FilePath, $Encoding)))) } [void] Import([PSCustomObject]$Object) { [ValidateNotNullOrEmpty()][PSCustomObject]$Object = $Object $Object | Get-Member -Type NoteProperty | Select-Object Name | ForEach-Object { $key = $_.Name; $val = $Object.$key; if ($null -ne $val) { $t = $this.PsObject.Properties.Where({ $_.Name -eq $key })[0].TypeNameOfValue $this.Set($key, ($val -as $t)); } } } [int] GetCount() { return ($this | Get-Member -Type *Property).count } [string[]] GetKeys() { return ($this | Get-Member -Type *Property).Name } [string] ToString() { $r = $this.ToArray(); $s = '' $shortnr = [scriptblock]::Create({ param([string]$str, [int]$MaxLength) while ($str.Length -gt $MaxLength) { $str = $str.Substring(0, [Math]::Floor(($str.Length * 4 / 5))) } return $str } ) if ($r.Count -gt 1) { $b = $r[0]; $e = $r[-1] $0 = $shortnr.Invoke("{'$($b.Keys)' = '$($b.values.ToString())'}", 40) $1 = $shortnr.Invoke("{'$($e.Keys)' = '$($e.values.ToString())'}", 40) $s = "@($0 ... $1)" } elseif ($r.count -eq 1) { $0 = $shortnr.Invoke("{'$($r[0].Keys)' = '$($r[0].values.ToString())'}", 40) $s = "@($0)" } else { $s = '@()' } return $s } } class cert { [string]$Public [string]$Private [string]$KeepLocal = $false [string]$Pfx } class ProjectConfig : EnvCfg { [string]$Name [string]$publicKey = "" [string]$remoteGistUrl = "" [string[]]$allowedUserIds = @() [string] ToString() { return ($this | ConvertTo-Json) } } class UserConfig : EnvCfg { [ValidateNotNullOrWhiteSpace()][string]$UserName = [UserConfig]::GetUserName() [ValidateNotNullOrWhiteSpace()][string]$UserId [ValidateNotNullOrEmpty()][string[]]$projects [ValidateRange(0, 73000)][int]$ExpiryDays = 1 [bool]$Use2FA = $false [cert]$cert UserConfig() : base() { [bool]$Is_Unix_Os = [bool](Get-Variable IsLinux -ValueOnly -ErrorAction Ignore) -or [bool](Get-Variable IsMacOS -ValueOnly -ErrorAction Ignore) [bool]$Is_Windows = [bool](Get-Variable IsWindows -ValueOnly -ErrorAction Ignore) [string]$CertPath = switch ($true) { $Is_Unix_Os { '/etc/ssl/private/'; break } $Is_Windows { [IO.Path]::Combine($env:CommonProgramFiles, 'SSL', 'Private'); break } Default { $PSScriptRoot } } $this.cert = New-Object cert -Property @{ Public = [IO.Path]::Combine($CertPath, "$($this.UserName).cert.pem") Private = [IO.Path]::Combine($CertPath, "$($this.UserName).key.pem"); Pfx = [IO.Path]::Combine($CertPath, "$($this.UserName).pfx") } } static [string] GetUserName() { $u = $env:USER; if (!$u) { $u = $env:USERNAME }; return $u } [string] ToString() { return ($this | ConvertTo-Json) } } class vars { hidden [ValidateNotNullOrEmpty()][char]$p = [IO.Path]::PathSeparator static [ValidateNotNullOrEmpty()][string[]]$targets = [Enum]::GetNames([EnvironmentVariableTarget]) vars() { $this.PsObject.properties.add([psscriptproperty]::new('Process', { $o = [Environment]::GetEnvironmentVariables('Process'); return $o.keys.ForEach({ [dotEntry]::new($_, $o["$_"], "ASSIGN") }) })) $this.PsObject.properties.add([psscriptproperty]::new('Machine', { $o = [Environment]::GetEnvironmentVariables('Machine'); return $o.keys.ForEach({ [dotEntry]::new($_, $o["$_"], "ASSIGN") }) })) $this.PsObject.properties.add([psscriptproperty]::new('User', { $o = [Environment]::GetEnvironmentVariables('User'); return $o.keys.ForEach({ [dotEntry]::new($_, $o["$_"], "ASSIGN") }) })) } [void] Refresh() { [System.Management.Automation.ActionPreference]$DbP2 = $(Get-Variable DebugPreference -ValueOnly); $DebugPreference = 'SilentlyContinue' # turn off debug for a while. (prevents spiting out all the C# code) try { if ([EnvTools]::GetHostOs() -eq "Windows") { $IsnmLoaded = [bool]("win32.nativemethods" -as [type]) $IswxLoaded = [bool]("Win32API.Explorer" -as [type]) if (!$IsnmLoaded -or !$IswxLoaded) { Write-Verbose "🔵 ⏳ Loading required namespaces ..."; [Console]::WriteLine() } if (!$IsnmLoaded) { Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition '[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);' } if (!$IswxLoaded) { Add-Type 'using System; using System.Runtime.InteropServices; namespace Win32API { public class Explorer { private static readonly IntPtr HWND_BROADCAST = new IntPtr (0xffff); private static readonly IntPtr HWND_KEYBOARD = new IntPtr (65535); private static readonly UIntPtr WM_USER = new UIntPtr (41504); private const Int32 WM_SETTINGCHANGE = 0x1a; private const Int32 SMTO_ABORTIFHUNG = 0x0002; private const Int32 VK_F5 = 273; [DllImport ("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern Int32 SHChangeNotify (Int32 eventId, Int32 flags, IntPtr item1, IntPtr item2); [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern IntPtr SendMessageTimeout (IntPtr hWnd, Int32 Msg, IntPtr wParam, String lParam, Int32 fuFlags, Int32 uTimeout, IntPtr lpdwResult); [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] static extern bool SendNotifyMessage (IntPtr hWnd, UInt32 Msg, IntPtr wParam, String lParam); [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern Int32 PostMessage (IntPtr hWnd, UInt32 Msg, UIntPtr wParam, IntPtr lParam); public static void RefreshEnvironment () { SHChangeNotify (0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero); SendMessageTimeout (HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "Environment", SMTO_ABORTIFHUNG, 100, IntPtr.Zero); SendNotifyMessage (HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "TraySettings"); } public static void RefreshShell () { PostMessage (HWND_KEYBOARD, VK_F5, WM_USER, IntPtr.Zero);}}}' } # Tiddy and updated version lives here: https://gist.github.com/alainQtec/e75089c849ccf5b02d0d1cfa6618fc3a/raw/2cdac0da416ea9f25a2e273d445c6a2d725bc6b7/Win32API.Explorer.cs # Refresh all objects using Win32API. ie: sometimes explorer.exe just doesn't get the message that things were updated. # RefreshEnvironment, RefreshShell and Notify all windows of environment block change [scriptblock]::Create("[Win32API.Explorer]::RefreshEnvironment(); [Win32API.Explorer]::RefreshShell()").Invoke() [scriptblock]::Create("`$HWND_BROADCAST = [intptr]0xffff; `$WM_SETTINGCHANGE = 0x1a; `$result = [uintptr]::zero; [void][win32.nativemethods]::SendMessageTimeout(`$HWND_BROADCAST, `$WM_SETTINGCHANGE, [uintptr]::Zero, 'Environment', 2, 5000, [ref]`$result)").Invoke() } foreach ($target in [vars]::targets) { Write-Verbose "🔵 [Refresh] Updating variables in [$target] scope..." $currentVars = $this.$target if (!$currentVars) { continue } foreach ($key in $currentVars.Keys) { $value = $currentVars[$key]; # TODO: Add a progressbar switch ($true) { ($key -eq 'Path') { [Environment]::SetEnvironmentVariable($key, [string]::Join($this.p, $this.getAllValues($key)), $target); break } ($key -eq "PSModulePath" -and [EnvTools]::GetHostOs() -eq "Windows") { $psm = @(); if ($(Get-Variable PSVersionTable -ValueOnly).psversion -ge [System.Version]("4.0.0.0")) { $psm += [System.IO.Path]::Combine(${env:ProgramFiles}, 'WindowsPowerShell', 'Modules') } if (!($this.User.ContainsKey($key))) { $psm += [System.IO.Path]::Combine($([environment]::GetFolderPath('MyDocuments')), 'WindowsPowerShell', 'Modules') } else { $psm += $this.User.$key -split $this.p } $psm += $this.getAllValues($key) [Environment]::SetEnvironmentVariable($key, [string]::Join($this.p, ($psm | Select-Object -Unique)), $target) break; } Default { [Environment]::SetEnvironmentVariable($key, $value, $target) } } } } } catch { Write-Host " [!] Unexpected Error while runing refreshScript." Write-Host " [!] [Mitigation] Using the Old 'Quick-refresh' method. (Still not reliable, but its better than just exiting without taking any action.) :" Write-Verbose " [Mitigation] [Refresh] ---------------- Refreshing PATH" $paths = 'Machine', 'User' | ForEach-Object { $([Environment]::GetEnvironmentVariable("PATH", "$_")) -split $j_ } | Select-Object -Unique $Env:PATH = $paths -join $this.p throw $_.Exception } finally { $DebugPreference = $DbP2; $this.cleanUp() } } hidden [void] cleanUp() { [int]$c = 0; [int]$t = $this::targets.Count; [Console]::WriteLine() foreach ($target in [vars]::targets) { Write-Verbose "🔵 [Refresh] $c/$t Cleanning obsolete variables in [$target] scope ..." $obsoletes = $this.$target.Keys.Where({ $this.ToString() -notcontains $_ }) if ($obsoletes) { foreach ($var_Name in $obsoletes) { Write-Verbose "🔵 [Refresh] Cleanning Env:Variable $var_Name in $target scope." $([scriptblock]::Create("[System.Environment]::SetEnvironmentVariable('$var_Name', `$null, [System.EnvironmentVariableTarget]::$target)")).Invoke(); if ($null -ne ${env:var_Name}) { Remove-Item -LiteralPath "${env:var_Name}" -Force -ErrorAction SilentlyContinue | Out-Null } } } else { Write-Verbose "🔵 [Refresh] No obsolete variables were found. ✅" } $this.$target.Keys.ForEach({ Set-Item -Path "Env:$_" -Value $this.$target[$_] }) $c++; [Console]::WriteLine() } } hidden [string[]] getAllValues([string]$Name) { $values = ($this::targets.ForEach({ [System.Environment]::GetEnvironmentVariable($Name.ToUpper(), $_) }) | Select-Object -Unique) if (!$values) { return @() } return $values } [string[]] ToString() { return ($this.Machine.Keys + $this.User.Keys + $this.Process.Keys + 'PSModulePath') | Sort-Object | Select-Object -Unique } } class EnvTools { static $X509CertHelper static [vars] $vars = [vars]::new() static [EnvCfg] $config = [EnvCfg]::new(@{ User = [UserConfig]::new(); Project = [ProjectConfig]::new() }) Static [IO.DirectoryInfo] $DataPath = (Get-DataPath 'dotEnv' 'Data') static hidden [string]$VarName_Suffix = [EnvTools].GUID.ToString().Replace('-', '_'); static [bool] $useDebug = (Get-Variable DebugPreference -ValueOnly) -eq 'Continue' hidden [System.Security.Cryptography.X509Certificates.X509Certificate2] $Cert EnvTools() {} static [void] refreshEnv() { [EnvTools]::refreshEnv([ctxOption]::None) } static [void] refreshEnv([ctxOption]$ctxOption) { try { $hostOS = [EnvTools]::GetHostOs(); $IsWinEnv = $hostOS -eq "Windows"; if ($hostOS -eq "Windows" -and ![EnvTools]::IsAdmin()) { Write-Warning " : [!] It seems You're not Admin [!] " return } if ($hostOS -eq "Windows" -and ![IO.File]::Exists($env:ObjectsRefreshScript) -and "$ctxOption" -ne "Remove") { Write-Verbose "🔵 ObjectsRefreshScript does not exist; Creating new one ..." [EnvTools]::CreateObjectsRefreshScript(); [Console]::WriteLine() } [EnvTools]::vars.Refresh() Write-Verbose "🔵 [Refresh] Done Now everything should be refreshed." } catch { Write-Verbose " [!] Unexpected Error while refreshing env:variables."; [Console]::WriteLine() throw $_.Exception } finally { if ($IsWinEnv) { $reg_path = 'HKLM:\SOFTWARE\Classes\DesktopBackground\shell\Refresh Explorer'; if ("$ctxOption" -eq "Add") { $kmd_Path = [IO.Path]::Combine($reg_path, "command"); $q = [char]34 # quote (Used to avoid escape chars) if (!$(Test-Path $reg_path -ErrorAction SilentlyContinue)) { New-Item -Path $kmd_Path -ItemType Directory -Force | Out-Null } New-ItemProperty -Path $reg_path -Name 'Icon' -Value 'Explorer.exe' -PropertyType String -Force | Out-Null New-ItemProperty -Path $reg_path -Name 'Position' -Value 'Bottom' -PropertyType String -Force | Out-Null New-ItemProperty -Path $kmd_Path -Name '(default)' -Value "Powershell.exe -NoLogo -WindowStyle Hidden -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $q$env:ObjectsRefreshScript$q" -PropertyType String -Force | Out-Null } if ("$ctxOption" -eq "Remove") { Write-Verbose " ⏳ Removing Registry Keys.." Remove-Item -Path $reg_path -Recurse -Force -ErrorAction SilentlyContinue Remove-Item $env:ObjectsRefreshScript -Force -ErrorAction SilentlyContinue [System.Environment]::SetEnvironmentVariable('ObjectsRefreshScript', $null, [System.EnvironmentVariableTarget]::Process) [System.Environment]::SetEnvironmentVariable('ObjectsRefreshScript', $null, [System.EnvironmentVariableTarget]::User) } } } } static [void] SetEnvironmentVariable([string]$Name, [string]$Value, [System.EnvironmentVariableTarget]$Scope) { if ($Name.ToUpper().Equals("PATH") -and [EnvTools]::GetHostOs() -eq "Windows") { $hive_is_connected = $false ([Microsoft.Win32.RegistryKey]$win32RegistryKey, [string]$registryKey) = switch ($Scope) { "Machine" { $dkey = "HKLM\DEFAULT"; $ntFl = "C:\Users\Default\NTUSER.DAT" if (!(Test-Path $dkey.Replace("\", ":"))) { Write-Verbose "Loading file $ntFl to the reg Key $dkey" $r = reg load $dkey $ntFl *>&1 if (!$?) { throw "Failed to load hive: $r" } }; $hive_is_connected = $true; $k = 'DEFAULT\Environment' [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($k), $k break; } "User" { $k = 'Environment' [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($k), $k; break; } Default { $k = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment\' [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($k), $k } } # "Write ACCESS CHECKING".. if ($null -eq $win32RegistryKey.OpenSubKey($registryKey, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree)) { Write-Warning "[!] No RegistryKeyReadWrite Permission." } try { $registryType = switch ($true) { $win32RegistryKey.GetValueNames().Contains($Name) { $win32RegistryKey.GetValueKind($Name); break } $Name.ToUpper().Equals("PATH") { [Microsoft.Win32.RegistryValueKind]::ExpandString; break } Default { [Microsoft.Win32.RegistryValueKind]::String } } } catch { throw "Error. Could not find reg type for $Name`n" + $_ } $CurrentPath = & { # idk, probably scope issues try { [System.Environment]::GetEnvironmentVariable('PATH', "$Scope") } catch { $win32RegistryKey.GetValue('PATH', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames).TrimEnd([System.IO.Path]::PathSeparator) } } $NewPathValue = if (($null -eq $Value) -or ($Value -eq '')) { $CurrentPath }else { [string]::Concat($value, [System.IO.Path]::PathSeparator, $CurrentPath) } if ($NewPathValue.Contains('%')) { $registryType = [Microsoft.Win32.RegistryValueKind]::ExpandString } [void]$win32RegistryKey.SetValue('PATH', $NewPathValue, $registryType) $win32RegistryKey.Handle.Close() Write-Verbose "Added PATH:Variable `"$Value`"." if ($hive_is_connected) { if ($PSCmdlet.ShouldProcess('HKLM\DEFAULT', 'reg unload')) { $r = reg unload "HKLM\DEFAULT" *>&1 } } return } Set-Item -Path Env:/$Name -Value $Value -Force [System.Environment]::SetEnvironmentVariable($Name, $Value, $Scope); if ([Bool][System.Environment]::GetEnvironmentVariable("$Name", $Scope)) { Write-Verbose "Set env:variable `"$Name`"." } } static [void] CreateObjectsRefreshScript() { $TempFile = $null; try { $Fl = New-TemporaryFile; $rF = [System.IO.Path]::ChangeExtension($Fl.FullName, 'ps1'); [System.IO.File]::Move($Fl.FullName, $rF); $TempFile = Get-Item $rf $rfScript = [scriptblock]::Create({ Import-Module dotEnv; Update-SessionEnv } ) $rfScript.ToString() | Set-Content -Path $TempFile $([scriptblock]::Create("[System.Environment]::SetEnvironmentVariable('ObjectsRefreshScript', '$($TempFile.FullName)', [System.EnvironmentVariableTarget]::Process)")).Invoke(); $([scriptblock]::Create("[System.Environment]::SetEnvironmentVariable('ObjectsRefreshScript', '$($TempFile.FullName)', [System.EnvironmentVariableTarget]::User)")).Invoke(); Write-Verbose "🔵 Updated env:ObjectsRefreshScript to $($TempFile.FullName)" } catch { Write-Verbose " [!] Error while Setting env:ObjectsRefreshScript to $TempFile"; [Console]::WriteLine() throw $_.Exception } } static [System.Object[]] RunAsync([scriptBlock]$command, [string]$StatusMsg) { # .SYNOPSIS # Run Commands using Background Runspaces Instead of PSJobs For Better Performance $Comdresult = $null; [ValidateNotNullOrEmpty()][scriptBlock]$command = $command $PsInstance = [System.Management.Automation.PowerShell]::Create().AddScript($command) $job = $PsInstance.BeginInvoke(); do { $ProgressPercent = if ([int]$job.TotalTime.TotalMilliseconds -ne 0) { [int]($job.RemainingTime.TotalMilliseconds / $job.TotalTime.TotalMilliseconds * 100) } else { 100 } Write-Progress -Activity "[dotEnv]" -Status "$StatusMsg" -PercentComplete $ProgressPercent Start-Sleep -Milliseconds 100 } until ($job.IsCompleted) Write-Progress -Activity "[dotEnv]" -Status "command Complete." -PercentComplete 100 if ($null -ne $PsInstance) { $Comdresult = $PsInstance.EndInvoke($job); $PsInstance.Dispose(); $PsInstance.Runspace.CloseAsync() } return $Comdresult } [guid] GetSessionId() { return [EnvTools]::GetSessionId($this) } static [guid] GetSessionId($HsmVault) { # .NOTES # - Creates fake guids, that are mainly used to create unique object names with a little bit of info added. $hash = $HsmVault.GetHashCode().ToString() return [guid]::new([System.BitConverter]::ToString([System.Text.Encoding]::UTF8.GetBytes(([string]::Concat(([char[]](97..102 + 65..70) | Get-Random -Count (16 - $hash.Length))) + $hash))).Replace("-", "").ToLower().Insert(8, "-").Insert(13, "-").Insert(18, "-").Insert(23, "-")) } static [bool] VerifyGetSessionId([guid]$guid, $HsmVault) { return $HsmVault.GetHashCode() -match $([string]::Concat([System.Text.Encoding]::UTF8.GetString($( { param([string]$HexString) $outputLength = $HexString.Length / 2; $output = [byte[]]::new($outputLength); $numeral = [char[]]::new(2); for ($i = 0; $i -lt $outputLength; $i++) { $HexString.CopyTo($i * 2, $numeral, 0, 2); $output[$i] = [Convert]::ToByte([string]::new($numeral), 16); } return $output; }.Invoke($guid.ToString().Replace('-', '')) ) ).ToCharArray().Where({ $_ -as [int] -notin (97..102 + 65..70) }) ) ) } static [bool] VerifyGetSessionId([string]$guid, $Source) { return [EnvTools]::VerifyGetSessionId([guid]$guid, $Source) } static [void] SetSessionCreds([guid]$sessionId) { [EnvTools]::SetSessionCreds([guid]$sessionId, $false) } static [void] SetSessionCreds([guid]$sessionId, [bool]$Force) { if (![string]::IsNullOrWhiteSpace([System.Environment]::GetEnvironmentVariable("$sessionId"))) { if (!$Force) { return } } [System.Environment]::SetEnvironmentVariable("$sessionId", $((Get-Credential -Message "Enter your Pfx Password" -Title "-----[[ PFX Password ]]-----" -UserName $env:username).GetNetworkCredential().SecurePassword | ConvertFrom-SecureString), [EnvironmentVariableTarget]::Process) } static [System.Security.Cryptography.X509Certificates.X509Certificate2] CreateSelfSignedCertificate([UserConfig]$UserCfg, [string]$sessionId) { [EnvTools]::SetSessionCreds([guid]$sessionId); [void][EnvTools]::GetX509CertHelper(); $Password = [System.Environment]::GetEnvironmentVariable($sessionId) | ConvertTo-SecureString return [EnvTools]::X509CertHelper::CreateSelfSignedCertificate("CN=$($UserCfg.UserName)", $UserCfg.cert.Private, $Password, 2048, [System.DateTimeOffset]::Now.AddDays(-1).DateTime, [System.DateTimeOffset]::Now.AddDays($UserCfg.ExpiryDays).DateTime) } static [Object] GetX509CertHelper() { $scriptNme = "X509CertHelper"; $X509VarName = "${scriptNme}_class_$([EnvTools]::VarName_Suffix)"; if (!$(Get-Variable $X509VarName -ValueOnly -Scope script -ErrorAction Ignore)) { try { $IsGitHubActions = $env:CI -eq 'true' -and $null -ne $env:GITHUB_RUN_ID $IsNotInstalled = $(if ($IsGitHubActions) { $true # (fails due to github API rate limit) Set-Variable -Name $X509VarName -Scope script -Option ReadOnly -Value ([scriptblock]::Create($((Invoke-RestMethod -Method Get https://api.github.com/gists/d8f277f1d830882c4927c144a99b70cd).files."$scriptNme.ps1".content))) } else { $null -eq (Get-InstalledScript -Name $scriptNme -Verbose:$false -ErrorAction Ignore)[0] } ) if ($IsNotInstalled) { Write-Host "[+] Installing script $scriptNme" -f Green [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -Verbose:$false; Install-Script -Name $scriptNme -Verbose:$false $Private:XscrContent = ([IO.File]::ReadAllText([IO.Path]::Combine((Get-InstalledScript -Name $scriptNme).InstalledLocation, "$scriptNme.ps1"))); Set-Variable -Name $X509VarName -Scope script -Option ReadOnly -Value ([scriptblock]::Create("$XscrContent")); } } catch { Write-Error "Unexpected error occurred: $($_.Exception); $($_.ScriptStackTrace)" } } $X509 = Get-Variable $X509VarName -ValueOnly -Scope script if ($X509) { . $X509; [EnvTools]::X509CertHelper = New-Object X509CertHelper } return [EnvTools]::X509CertHelper } static hidden [void] Resolve_modules([string[]]$Names) { $varName = "resolver_script_$([EnvTools]::VarName_Suffix)"; if (!$(Get-Variable $varName -ValueOnly -Scope script -ErrorAction Ignore)) { # Fetch it Once only, To Avoid spamming the github API :) Set-Variable -Name $varName -Scope script -Option ReadOnly -Value ([scriptblock]::Create($((Invoke-RestMethod -Method Get https://api.github.com/gists/7629f35f93ae89a525204bfd9931b366).files.'Resolve-Module.ps1'.content))) } $resolver_script = Get-Variable $varName -ValueOnly -Scope script if ($resolver_script) { . $resolver_script; Resolve-module -Name $Names } else { throw "Failed to fetch resolver script!" } } static [string] GetHostOs() { #TODO: refactor so that it returns one of these: [Enum]::GetNames([System.PlatformID]) return $(if ($(Get-Variable PSVersionTable -Value).PSVersion.Major -le 5 -or $(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }); } static [bool] IsAdmin() { $hostOs = [EnvTools]::GetHostOs() $isAdmn = switch ($hostOS) { "Windows" { (New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator); break } "Linux" { (& id -u) -eq 0; break } "MacOsx" { Write-Warning "MacOsx !! idk how to solve this one!"; $false; break } Default { throw "UNSUPPORTED_OS" } } return $isAdmn } } #endregion classes #region functions function Set-EnvConfig { # .SYNOPSIS # Supposed to run on-time, during module initial setup. It prepares Credentials to use when securing environment variables on local machine. # .DESCRIPTION # Generates a secure hashed credential file and configuration for the dotEnv module. # Has options to choose between DPAPI or AES encryption modes. # DPAPI is more secure but requires to be run by the same user account on the same windows machine. # AES is also secure but can be used when service account cannot be used to run in interactive mode. # .NOTES # Information or caveats about the function e.g. 'This function is not supported in Linux' # .LINK # Specify a URI to a help page, this will show when Get-Help -Online is used. # .EXAMPLE # Set-dotEnvConfig # Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines [CmdletBinding(SupportsShouldProcess = $true)] [Alias("Initialize-dotEnv")] param () process { if ($PSCmdlet.ShouldProcess("Localhost", "Initialize dotEnv")) { # do stuff here # Write-Host "Hello from Private/cliHelper.env.Config/Set-dotEnvConfig" -f Green # [EnvTools]::config.Set("Path", (CryptoBase)::GetUnResolvedPath([IO.Path]::Combine([EnvTools]::DataPath, "Config.enc"))) } } end { } } function Resolve-FilePath { # .SYNOPSIS # Resolve FilePath # .DESCRIPTION # Gets the full Path of any file in a repo # .INPUTS # [string[]] # .OUTPUTS # [String[]] # .EXAMPLE # Resolve-FilePath * -Extensions ('.ps1', '.psm1') # Will get paths of powershell files in current location; thus [ModuleX]::ParseFile("*") will parse any powershell file in current location. # .EXAMPLE # Resolve-FilePath "Tests\Resources\Test-H*", "Tests\Resources\Test-F*" # .EXAMPLE # Resolve-FilePath ..\*.Tests.ps1 # .NOTES # Created to work with the "ModuleX" module. (Its not tested for other use cases) # TopLevel directory search takes Priority. # eg: Resolve-FilePath ModuleX.ps1 will return ./.env instead of ./BuildOutput/module/0.1.0/.env # Unless ./.env doesn't exist; In that case it will Recursively search for other Names in the repo. # .LINK # https://github.com/alainQtec/cliHelper.env/blob/main/Private/cliHelper.env.Utils/cliHelper.env.Utils.psm1 # [CmdletBinding(DefaultParameterSetName = 'Query')] [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Query')] [ValidateNotNullOrEmpty()] [Alias('Path')] [string]$Query, [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'Paths')] [ValidateNotNullOrEmpty()] [string[]]$Paths, [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $false, ParameterSetName = '__AllParameterSets')] [ValidateNotNullOrEmpty()] [Alias('Extension')] [string[]]$Extensions, [Parameter(Mandatory = $false, Position = 2, ValueFromPipeline = $false, ParameterSetName = '__AllParameterSets')] [string[]]$Exclude, [switch]$throwOnFailure, [switch]$NoAmbiguous ) begin { $pathsToSearch = @(); $resolved = @(); $error_Msg = $null; $throwOnFailure = [string]$ErrorActionPreference -eq 'Stop' $pathsToSearch += if ($PSCmdlet.ParameterSetName.Equals('Query')) { @($Query) } else { $Paths } $GitHubRoot = $(if (Get-Command -Name git -CommandType Application -ErrorAction Ignore) { git rev-parse --show-toplevel }else { $null }) -as [IO.DirectoryInfo] $GetFiles = [scriptblock]::Create({ param ([Parameter(Mandatory)][string]$qr) $f = Get-ChildItem -Path $qr -File -ErrorAction Ignore if ($PSBoundParameters.ContainsKey('Extensions')) { return ($Files | Where-Object { $_.Extension -in $Extensions }) }; return $f } ) [string[]]$Exclude = [IO.File]::ReadAllLines([IO.Path]::Combine($ExecutionContext.SessionState.Path.CurrentLocation, '.gitignore')).Where({ !$_.StartsWith('#') -and ![string]::IsNullOrWhiteSpace($_) }) } process { forEach ($p in $pathsToSearch) { if ([Regex]::IsMatch($p, '^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:[0-9]+)?\/?.*$')) { $error_Msg += " '$p' is a Url! Please provide a valid File Path."; continue } # TopLevel directory search: $rslvdPaths, $error_Msg = $validPaths, $null [string[]]$rslvdPaths = (Resolve-Path $p -ErrorAction Ignore).Path [string[]]$validPaths = ($rslvdPaths | Where-Object { (Test-Path -Path "$_" -PathType Any -ErrorAction Ignore) }) if ($validPaths.Count -gt 1 -and $NoAmbiguous) { $error_Msg += "Path '$p' is ambiguous: $($validPaths -join ', ')" } $Files = $GetFiles.Invoke($p); if ($Files.FullName) { $resolved += $Files.FullName; Continue } $q = $p; $p = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p); if ((Test-Path -Path $GitHubRoot.FullName -PathType Container -ErrorAction Ignore)) { $rslvdPaths = $( # Multi-Level directory search / -Recurse : switch ($true) { ([IO.Path]::IsPathFullyQualified($q)) { Get-Item -Path $q -ErrorAction Ignore break } $(![IO.Path]::IsPathFullyQualified($q) -and $q.Contains([IO.Path]::DirectorySeparatorChar)) { $relPath = '([IO.Path]::GetRelativePath($ExecutionContext.SessionState.Path.CurrentLocation, $_.FullName))' $IsMatch = if ($q.Contains('*')) { [scriptblock]::Create("$relPath -like `"$q`" -or `$_.FullName -like `"$q`"") } elseif ($q.EndsWith([IO.Path]::DirectorySeparatorChar)) { [scriptblock]::Create("$relPath -like `"$q*`" -or `$_.FullName -like `"$q*`"") } else { [scriptblock]::Create("$relPath -eq `"$q`" -or `$_.FullName -eq `"$q`"") } $(Get-ChildItem -Path $GitHubRoot.FullName -File -Recurse -ErrorAction Ignore).Where($IsMatch) break } $(![IO.Path]::IsPathFullyQualified($q) -and !$q.Contains([IO.Path]::DirectorySeparatorChar)) { $IsMatch = if ($q.Contains('*')) { [scriptblock]::Create('$_.Name -like $q -or $_.BaseName -like $q') } else { [scriptblock]::Create('$_.Name -eq $q -or $_.BaseName -eq $q') } $(Get-ChildItem -Path $GitHubRoot.FullName -File -Recurse -ErrorAction Ignore).Where($IsMatch) break } Default { Get-ChildItem -Path $GitHubRoot.FullName -File -Recurse -Filter $q -ErrorAction Ignore } } ) | Select-Object -ExpandProperty FullName }; if (!$rslvdPaths) { $error_Msg += "No files were found in Path '$p'."; Continue } $resolved += $rslvdPaths } $resolved = $resolved | Sort-Object -Unique if ($PSBoundParameters.ContainsKey('Extensions')) { $resolved = $($resolved -as [IO.FileInfo[]] | Where-Object { $_.Extension -in $Extensions }).FullName } if ($resolved.Count -gt 1 -and $NoAmbiguous) { $error_Msg += ' Error: Resolved to Multiple paths' } } end { if ($error_Msg) { if ($throwOnFailure) { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::New( [System.Management.Automation.ItemNotFoundException]::new($error_Msg), 'ItemNotFoundException', 'OperationStopped', [PSCustomObject]@{ Params = $PSCmdlet.MyInvocation.BoundParameters } ) ) } else { Write-Verbose $error_Msg } } return $resolved } } function Get-DataPath { [CmdletBinding()] [OutputType([System.IO.DirectoryInfo])] param ( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$appName, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [string]$SubdirName, [switch]$DontCreate ) process { $_Host_OS = Get-HostOs $dataPath = if ($_Host_OS -eq 'Windows') { [System.IO.DirectoryInfo]::new([IO.Path]::Combine($Env:HOME, "AppData", "Roaming", $appName, $SubdirName)) } elseif ($_Host_OS -in ('Linux', 'MacOs')) { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } elseif ($_Host_OS -eq 'Unknown') { try { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } catch { Write-Warning "Could not resolve chat data path" Write-Warning "HostOS = '$_Host_OS'. Could not resolve data path." [System.IO.Directory]::CreateTempSubdirectory(($SubdirName + 'Data-')) } } else { throw [InvalidOperationException]::new('Could not resolve data path. Get-HostOS FAILED!') } if (!$dataPath.Exists -and !$DontCreate.IsPresent) { New-Directory -Path $dataPath.FullName } return $dataPath } } function Get-HostOs() { end { return [EnvTools]::GetHostOs() } } function New-Directory { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] [OutputType([void])] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$Path ) process { [ValidateNotNullOrEmpty()][System.IO.DirectoryInfo]$Path = $Path $nF = @(); $p = $Path; while (!$p.Exists) { $nF += $p; $p = $p.Parent } [Array]::Reverse($nF); $nF | ForEach-Object { if ($PSCmdlet.ShouldProcess("$($_.FullName)", "Create")) { $_.Create(); Write-Debug "Created $_" } } } } #endregion functions |