cliHelper.env.psm1
#!/usr/bin/env pwsh using namespace System.IO using namespace System.Management.Automation.Language #Requires -Modules clihelper.xcrypt #region Classes 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 ([xcrypt]::Get_Host_Os() -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 [xcrypt]::Get_Host_Os() -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 } } # .SYNOPSIS # Module main class class dotEnv { static $X509CertHelper static [vars] $vars = [vars]::new() static [EnvCfg] $config = [EnvCfg]::new(@{ User = [UserConfig]::new(); Project = [ProjectConfig]::new() }) Static [IO.DirectoryInfo] $DataPath = [xcrypt]::Get_dataPath('dotEnv', 'Data') static hidden [string]$VarName_Suffix = [dotEnv].GUID.ToString().Replace('-', '_'); static [bool] $useDebug = (Get-Variable DebugPreference -ValueOnly) -eq 'Continue' hidden [System.Security.Cryptography.X509Certificates.X509Certificate2] $Cert dotEnv() { [dotEnv]::SetEnvFile(); Set-EnvConfig } static [string] Get([string]$key) { [ValidateNotNullOrEmpty()][string]$key = $key return [dotEnv]::Read([dotEnv].EnvFile).Where({ $_.Name -eq $key }).Value } static [dotEntry[]] Read([string]$EnvFile) { [ValidateNotNullOrEmpty()][string]$EnvFile = $(Resolve-Path $EnvFile -ea Ignore).Path $res_Obj = @(); $content = [IO.File]::ReadAllLines($EnvFile) if ([string]::IsNullOrWhiteSpace($content)) { Write-Debug "The .env file is empty!" return $res_Obj } foreach ($line in $content) { if ([string]::IsNullOrWhiteSpace($line)) { continue } if ($line.StartsWith("#") -or $line.StartsWith("//")) { Write-Verbose "🔵 ~ comment: $([dotEnv]::sensor($line))" continue } ($m, $d ) = switch -Wildcard ($line) { "*:=*" { "Prefix", ($line -split ":=", 2); Break } "*=:*" { "Suffix", ($line -split "=:", 2); Break } "*=*" { "Assign", ($line -split "=", 2); Break } Default { throw 'Unable to find Key value pair in line' } } $res_Obj += [dotEntry]::new($d[0].Trim(), $d[1].Trim(), $m) } return $res_Obj } static [void] Update([IO.FileInfo]$EnvFile, [string]$Name, [string]$Value) { [dotEnv]::Update($EnvFile, $Name, $Value, $false) } static [void] Update([IO.FileInfo]$EnvFile, [string]$Name, [string]$Value, [bool]$StripComments) { $Entries = [dotenv]::Read($EnvFile.FullName); if ($StripComments) { [IO.File]::WriteAllText($EnvFile, $([dotEnv]::Update($Entries, $Name, $Value).ForEach({ $_.ToString() }) | Out-String).Trim(), [System.Text.Encoding]::UTF8 ) } else { $q = $Entries.Where({ $_.Name -eq $Name }); $s = '' $sb = [System.Text.StringBuilder]::new([IO.File]::ReadAllText($EnvFile.FullName)); $ms = [PSObject]@{ Assign = '='; Prefix = ":="; Suffix = "=:" }; if ($q.count -ne 0) { $s = $ms[$q.Action] $pa = "(?m)^($Name{0}).*$" -f $s; $re = "$Name{0}$value" -f $s $updatedContent = $sb.ToString() -replace $pa, $re [IO.File]::WriteAllText($EnvFile.FullName, $updatedContent) } else { Write-Debug "key $Name was not found. Addind new one ..." $Entries += [dotEntry]::new($Name, $Value, "Assign") [IO.File]::WriteAllText($EnvFile.FullName, $($Entries.ForEach({ $_.ToString() }) | Out-String)) } } } static hidden [dotEntry[]] Update([dotEntry[]]$Entries, [string]$Name, [string]$Value) { return $Entries.ForEach({ if ($_.Name -eq $Name) { $_.Set($Name, $Value) } }) } static [void] Set([string]$EnvFile) { [dotEnv]::Set([dotEnv]::Read($EnvFile)) } static [void] Set([dotEntry[]]$Entries) { foreach ($item in $Entries) { switch ($item.Action) { "Assign" { [Environment]::SetEnvironmentVariable($item.Name, $item.value, "Process") | Out-Null } "Prefix" { $item.value = "{0};{1}" -f $item.value, [System.Environment]::GetEnvironmentVariable($item.Name) [Environment]::SetEnvironmentVariable($item.Name, $item.value, "Process") | Out-Null } "Suffix" { $item.value = "{1};{0}" -f $item.value, [System.Environment]::GetEnvironmentVariable($item.Name) [Environment]::SetEnvironmentVariable($item.Name, $item.value, "Process") | Out-Null } Default { throw [System.IO.InvalidDataException]::new() } } } } static [void] StripComments([string]$EnvFile) { [dotEnv]::StripComments($EnvFile, [System.Text.Encoding]::UTF8) } static [void] StripComments([string]$EnvFile, [System.Text.Encoding]$Encoding) { [ValidateNotNullOrEmpty()][string]$EnvFile = $(Resolve-Path $EnvFile -ea Ignore).Path [string]$content = ([dotenv]::Read($EnvFile).ForEach({ $_.ToString() }) | Out-String).Trim() [IO.File]::WriteAllText($EnvFile, $content, $Encoding) } static [bool] IsPersisted([string]$source) { $p = @(); $p += [dotEnv]::Config.Persisted; return $p.Contains($source) } static [void] Persist([string]$source) { $p = @(); $p += [dotEnv]::Config.Persisted; if (!$p.Contains($source)) { $p += $source; [dotEnv]::Config.Set("Persisted", $p) } } static [void] Unpersist([string]$source) { $p = @(); $p += [dotEnv]::Config.Persisted; if (!$p.Contains($source)) { $p = $p.where({ $_ -ne $source }) [dotEnv]::Config.Set("Persisted", $p) } } static [string] sensor([string]$str) { if ([string]::IsNullOrWhiteSpace($str)) { return $str } $_90 = [int][Math]::Floor($str.Length * .9) $_10 = [int][Math]::Floor($str.Length * .1) $_sr = ($str.Substring(0, $_10) + '░' * $_90) $_cs = "CENSORED"; if ($_sr.Length -gt 13) { $50l = [int][Math]::Floor($_sr.Length * .5) $_sr = $_sr.Substring(0, $50l - $_cs.Length) + $_cs + $_sr.Substring($50l + $_cs.Length) } return $_sr } static [void] SetEnvFile() { [dotEnv].PsObject.properties.add([psscriptproperty]::new('EnvFile', { return [IO.Path]::Combine($(Get-Variable executionContext -ValueOnly).SessionState.Path.CurrentLocation.Path, '.env') })) [dotEnv].PsObject.properties.add([psscriptproperty]::new('enc_envfile', { return [IO.Path]::Combine($(Get-Variable executionContext -ValueOnly).SessionState.Path.CurrentLocation.Path, '.env.enc') })) } static [void] refreshEnv() { [dotEnv]::refreshEnv([ctxOption]::None) } static [void] refreshEnv([ctxOption]$ctxOption) { try { $hostOS = [xcrypt]::Get_Host_Os(); $IsWinEnv = $hostOS -eq "Windows"; if ($hostOS -eq "Windows" -and ![dotEnv]::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 ..." [dotEnv]::CreateObjectsRefreshScript(); [Console]::WriteLine() } [dotEnv]::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 [xcrypt]::Get_Host_Os() -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 } } [guid] GetSessionId() { return [dotEnv]::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 [dotEnv]::VerifyGetSessionId([guid]$guid, $Source) } static [void] SetSessionCreds([guid]$sessionId) { [dotEnv]::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) { [dotEnv]::SetSessionCreds([guid]$sessionId); [void][dotEnv]::GetX509CertHelper(); $Password = [System.Environment]::GetEnvironmentVariable($sessionId) | ConvertTo-SecureString return [dotEnv]::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_$([dotEnv]::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; [dotEnv]::X509CertHelper = New-Object X509CertHelper } return [dotEnv]::X509CertHelper } static [bool] IsAdmin() { $hostOs = [xcrypt]::Get_Host_Os() $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! (wip)"; $false; break } Default { throw "UNSUPPORTED_OS" } } return $isAdmn } } #endregion Classes #region typeAccelerators # Types that will be available to users when they import the module. $typestoExport = @( [dotEnv] ) $TypeAcceleratorsClass = [PsObject].Assembly.GetType('System.Management.Automation.TypeAccelerators') foreach ($Type in $typestoExport) { if ($Type.FullName -in $TypeAcceleratorsClass::Get.Keys) { $Message = @( "Unable to register type accelerator '$($Type.FullName)'" 'Accelerator already exists.' ) -join ' - ' [System.Management.Automation.ErrorRecord]::new( [System.InvalidOperationException]::new($Message), 'TypeAcceleratorAlreadyExists', [System.Management.Automation.ErrorCategory]::InvalidOperation, $Type.FullName ) | Write-Warning } } # Add type accelerators for every exportable type. foreach ($Type in $typestoExport) { $TypeAcceleratorsClass::Add($Type.FullName, $Type) } # Remove type accelerators when the module is removed. $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { foreach ($Type in $typestoExport) { $TypeAcceleratorsClass::Remove($Type.FullName) } }.GetNewClosure(); $scripts = @(); $Public = Get-ChildItem "$PSScriptRoot/Public" -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue $scripts += Get-ChildItem "$PSScriptRoot/Private" -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue $scripts += $Public foreach ($file in $scripts) { Try { if ([string]::IsNullOrWhiteSpace($file.fullname)) { continue } . "$($file.fullname)" } Catch { Write-Warning "Failed to import function $($file.BaseName): $_" $host.UI.WriteErrorLine($_) } } $Param = @{ Function = $Public.BaseName Cmdlet = '*' Alias = '*' Verbose = $false } Export-ModuleMember @Param |