From 8a4db2bdd143250f55580656fb091a349a6af6e6 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Thu, 21 Jan 2021 20:16:09 -0800 Subject: [PATCH 1/3] Migrating Invoke-CPowerShell (and dependencies) from Carbon. --- .gitignore | 4 +- Carbon.Core/Carbon.Core.psd1 | 191 +++++++++++++++ .../Carbon.Core.psm1 | 13 + Carbon.Core/Functions/ConvertTo-CBase64.ps1 | 198 +++++++++++++++ Carbon.Core/Functions/Get-CPowerShellPath.ps1 | 105 ++++++++ Carbon.Core/Functions/Invoke-CPowerShell.ps1 | 154 ++++++++++++ .../Functions/Test-COperatingSystem.ps1 | 95 ++++++++ Carbon.Core/Functions/Test-CPowerShell.ps1 | 67 +++++ .../Functions/Use-CallerPreference.ps1 | 0 Carbon.Core/Import-Carbon.Core.ps1 | 53 ++++ Carbon.Core/en-US/about_Carbon.Core.help.txt | 45 ++++ Carbon.Management/Carbon.Management.psd1 | 120 --------- .../Functions/FunctionTemplate.ps1 | 26 -- .../Import-Carbon.Management.ps1 | 39 --- .../en-US/about_Carbon.Management.help.txt | 9 - LICENSE | 202 ++++++++++++++++ NOTICE | 3 + README.md | 36 +-- ...gement.Tests.ps1 => Carbon.Core.Tests.ps1} | 14 +- .../Carbon.CoreTestHelper.psm1 | 11 + .../Carbon.ManagementTestHelper.psm1 | 0 Tests/ConvertTo-CBase64.Tests.ps1 | 228 ++++++++++++++++++ Tests/Get-CPowerShellPath.Tests.ps1 | 104 ++++++++ ...Tests.ps1 => Import-Carbon.Core.Tests.ps1} | 16 +- ...ManagementTest.ps1 => Initialize-Test.ps1} | 8 +- Tests/Invoke-CPowerShell.Tests.ps1 | 175 ++++++++++++++ Tests/Test-COperatingSystem.Tests.ps1 | 110 +++++++++ Tests/Test-CPowerShell.Tests.ps1 | 70 ++++++ Tests/users.psd1 | 8 + init.ps1 | 94 ++++++++ reset.ps1 | 39 +++ whiskey.yml | 38 ++- 32 files changed, 2035 insertions(+), 240 deletions(-) create mode 100644 Carbon.Core/Carbon.Core.psd1 rename Carbon.Management/Carbon.Management.psm1 => Carbon.Core/Carbon.Core.psm1 (58%) create mode 100644 Carbon.Core/Functions/ConvertTo-CBase64.ps1 create mode 100644 Carbon.Core/Functions/Get-CPowerShellPath.ps1 create mode 100644 Carbon.Core/Functions/Invoke-CPowerShell.ps1 create mode 100644 Carbon.Core/Functions/Test-COperatingSystem.ps1 create mode 100644 Carbon.Core/Functions/Test-CPowerShell.ps1 rename {Carbon.Management => Carbon.Core}/Functions/Use-CallerPreference.ps1 (100%) create mode 100644 Carbon.Core/Import-Carbon.Core.ps1 create mode 100644 Carbon.Core/en-US/about_Carbon.Core.help.txt delete mode 100644 Carbon.Management/Carbon.Management.psd1 delete mode 100644 Carbon.Management/Functions/FunctionTemplate.ps1 delete mode 100644 Carbon.Management/Import-Carbon.Management.ps1 delete mode 100644 Carbon.Management/en-US/about_Carbon.Management.help.txt create mode 100644 LICENSE create mode 100644 NOTICE rename Tests/{Carbon.Management.Tests.ps1 => Carbon.Core.Tests.ps1} (73%) create mode 100644 Tests/Carbon.CoreTestHelper/Carbon.CoreTestHelper.psm1 delete mode 100644 Tests/Carbon.ManagementTestHelper/Carbon.ManagementTestHelper.psm1 create mode 100644 Tests/ConvertTo-CBase64.Tests.ps1 create mode 100644 Tests/Get-CPowerShellPath.Tests.ps1 rename Tests/{Import-Carbon.Management.Tests.ps1 => Import-Carbon.Core.Tests.ps1} (51%) rename Tests/{Initialize-Carbon.ManagementTest.ps1 => Initialize-Test.ps1} (80%) create mode 100644 Tests/Invoke-CPowerShell.Tests.ps1 create mode 100644 Tests/Test-COperatingSystem.Tests.ps1 create mode 100644 Tests/Test-CPowerShell.Tests.ps1 create mode 100644 Tests/users.psd1 create mode 100644 init.ps1 create mode 100644 reset.ps1 diff --git a/.gitignore b/.gitignore index 0332694..3527023 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /.output -/PSModules \ No newline at end of file +/PSModules +/.vscode +/Tests/.password \ No newline at end of file diff --git a/Carbon.Core/Carbon.Core.psd1 b/Carbon.Core/Carbon.Core.psd1 new file mode 100644 index 0000000..6f31dab --- /dev/null +++ b/Carbon.Core/Carbon.Core.psd1 @@ -0,0 +1,191 @@ +# Copyright Aaron Jensen and WebMD Health Services +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'Carbon.Core.psm1' + + # Version number of this module. + ModuleVersion = '1.0.0' + + # ID used to uniquely identify this module + GUID = '20DA9F42-23C4-4917-8597-DCFD7EE4AD00' + + # Author of this module + Author = 'WebMD Health Services' + + # Company or vendor of this module + CompanyName = 'WebMD Health Services' + + # If you want to support .NET Core, add 'Core' to this list. + CompatiblePSEditions = @( 'Desktop', 'Core' ) + + # Copyright statement for this module + Copyright = '(c) 2021 Aaron Jensen and WebMD Health Services.' + + # Description of the functionality provided by this module + Description = 'Functions that make doing things in PowerShell a little easier. We think these should be part of PowerShell itself. Core functions that are used by other Carbon modules.' + + # Minimum version of the Windows PowerShell engine required by this module + PowerShellVersion = '5.1' + + # Name of the Windows PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the Windows PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module + # CLRVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @( ) + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @( ) + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module. Only list public function here. + FunctionsToExport = @( + 'ConvertTo-CBase64', + 'Get-CPowerShellPath', + 'Invoke-CPowerShell', + 'Start-CPowerShellProcess', + 'Test-COperatingSystem', + 'Test-CPowerShell' + ) + + # Cmdlets to export from this module. By default, you get a script module, so there are no cmdlets. + # CmdletsToExport = @() + + # Variables to export from this module. Don't export variables except in RARE instances. + VariablesToExport = @() + + # Aliases to export from this module. Don't create/export aliases. It can pollute your user's sessions. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @( + 'Carbon', 'Desktop', 'Core', 'encoding', 'convert', 'convertto', 'text', 'base64', 'invoke', 'os', + 'operating-system', 'architecture', 'powershell', 'pwsh', 'runas', 'credential', 'x86', 'x64', + 'windows', 'linux', 'macos' ) + + # A URL to the license for this module. + LicenseUri = 'http://www.apache.org/licenses/LICENSE-2.0' + + # A URL to the main website for this project. + ProjectUri = 'https://whsbitbucket.webmd.net/projects/POWERSHELL/repos/Carbon.Core/browse' + + # A URL to an icon representing this module. + # IconUri = '' + + Prerelease = '' + + # ReleaseNotes of this module + ReleaseNotes = @' +# Upgrade Instructions + +We're breaking up Carbon into smaller and more targeted modules. Hopefully, this will help maintenance, and make it +easier to use Carbon across many versions and editions of PowerShell. This module will be the place where core functions +used by other Carbon modules will be put. + +If you're upgrading from Carbon to this module, you should do the following: + +* Replace usages of `Test-COSIs64Bit` with `Test-COperatingSystem -Is64Bit`. +* Replace usages of `Test-COSIs32Bit` with `Test-COperatingSystem -Is32Bit`. +* Replace usages of `Test-CPowerShellIs32Bit` with `Test-CPowerShell -Is32Bit`. +* Replace usages of `Test-CPowerShellIs64Bit` with `Test-CPowerShell -Is64Bit`. +* Rename usages of the `ConvertTo-CBase64` function's `Value` parameter to `InputObject`, or pipe the value to the +function instead. + +We made a lot of changes to the `Invoke-CPowerShell` function: + +* The `Invoke-CPowerShell` function no longer allows you to pass script blocks. Instead, convert your script block to +a string, and pass that string to the `Command` parameter. This will base64 encode the command and pass it to +PowerShell's -EncodedCommand property. +* The `Invoke-CPowerShell` function no longer has `FilePath`, `OutputFormat`, `ExecutionPolicy`, `NonInteractive`, +or `Runtime` parameters. Instead, pass these as arguments to the `ArgumentList` parameter, e.g. +`-ArgumentList @('-NonInteractive', '-ExecutionPolicy', 'Bypasss'). You are now responsible for passing all PowerShell +arguments via the `ArgumentList` parameter. +* The `Invoke-CPowerShell` function no longer supports running PowerShell 2 under .NET 4. +* Remove the `-Encode` switch. `Invoke-CPowerShell` now always base64 encodes the value of the `Command` parameter. +* The `Invoke-CPowerShell` function only accepts strings to the `-Command` parameter. Check all usages to ensure you're +passing a string. +* The `Invoke-CPowerShell` function now returns output when running PowerShell as a different user. You may see more +output in your scripts. + + +# Changes Since Carbon 2.9.4 + +* Migrated `Invoke-CPowerShell` and `ConvertTo-CBase64` from Carbon. +* `ConvertTo-CBase64` now converts chars, ints (signed and unsigned, 16, 32, and 64-bit sizes), floats, and doubles to +base64. You can now also pipe an array of bytes or chars and it will collect each item in the array and encode them at +as one unit. +* Renamed the `ConvertTo-CBase64` function's `Value` parameter to `InputObject`. +* Created `Test-COperatingSystem` function that can test if the current OS is 32-bit, 62-bit, Windows, Linux, and/or +macOS. This function was adapted from and replaces's Carbon's `Test-COSIs64Bit` and `Test-COSIs32Bit`. +* Created `Test-CPowerShell` function that can test if the current PowerShell instance is 32-bit, 64-bit, Core edition, +or Desktop edition. It treats versions of PowerShell that don't specify a version as "Desktop". This function was +adapted from and replaces Carbon's `Test-CPowerShellIs32Bit` and `Test-CPowerShellIs64Bit` functions. +* `Invoke-CPowerShell` now works on Linux and macOS. On Windows, it will start a new PowerShell process using the same +edition. If you want to use a custom version of PowerShell, pass the path to the PowerShell executable to use to the +new `Path` parameter. + +# Known Issues +* There is a bug in PowerShell Core on Linux/macOS that fails when running `Start-Job` with a custom credential. The +`Invoke-CPowerShell` function will fail when run on Linux/MacOS using a custom credential. See +https://github.com/PowerShell/PowerShell/issues/7172 for more information. +'@ + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' +} diff --git a/Carbon.Management/Carbon.Management.psm1 b/Carbon.Core/Carbon.Core.psm1 similarity index 58% rename from Carbon.Management/Carbon.Management.psm1 rename to Carbon.Core/Carbon.Core.psm1 index 0489b30..5ec70d8 100644 --- a/Carbon.Management/Carbon.Management.psm1 +++ b/Carbon.Core/Carbon.Core.psm1 @@ -1,3 +1,16 @@ +# Copyright Aaron Jensen and WebMD Health Services +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License #Requires -Version 5.1 Set-StrictMode -Version 'Latest' diff --git a/Carbon.Core/Functions/ConvertTo-CBase64.ps1 b/Carbon.Core/Functions/ConvertTo-CBase64.ps1 new file mode 100644 index 0000000..d15acce --- /dev/null +++ b/Carbon.Core/Functions/ConvertTo-CBase64.ps1 @@ -0,0 +1,198 @@ + +function ConvertTo-CBase64 +{ + <# + .SYNOPSIS + Base64 encodes things. + + .DESCRIPTION + The `ConvertTo-CBase64` function base64 encodes things. Pipe what you want to encode to `ConvertTo-CBase64`. The + function can encode: + + * [String] + * [byte] + * [char] + * Signed integers: [int16], [int], [int64] (i.e. [long]) + * Unsigned integers: [uint16], [uint32], [uint64] + * Floating point numbers: [float], [double] + * [bool] + + For each item piped to `ConvertTo-CBase64`, the function returns that item base64 encoded. + + If you pipe all bytes or all chars to `ConvertTo-CBase64`, it will encode all the bytes and chars together. This + allows you to do this: + + [IO.File]::ReadAllBytes('some file') | ConvertTo-CBase64 + + and get back a single string for all the bytes/chars. + + By default, `ConvertTo-CBase64` uses Unicode/UTF-16 encoding when converting strings to base64 (this is the default + encoding of strings by .NET and PowerShell). To use a different encoding, pass it to the `Encoding` parameter + (`[Text.Encoding] | Get-Member -Static` will show all the default encodings). + + .EXAMPLE + 'Encode me, please!' | ConvertTo-CBase64 + + Demonstrates how to encode a string in base64. + + .EXAMPLE + 'Encode me, please!' | ConvertTo-CBase64 -Encoding ([Text.Encoding]::ASCII) + + Demonstrates how to use a custom encoding when converting a string to base64. The parenthesis around the encoding + is required by the PowerShell language. + + .EXAMPLE + [IO.File]::ReadAllBytes('path to some file') | ConvertTo-CBase64 + + Demonstrates that you can pipe an array of bytes to `ConvertTo-CBase64` and you'll get back a single string of all + the bytes base64 encoded. + + .EXAMPLE + [IO.File]::ReadAllText('path to some file').ToCharArray() | ConvertTo-CBase64 + + Demonstrates that you can pipe an array of chars to `ConvertTo-CBase64` and you'll get back a single string of all + the chars base64 encoded. + + .EXAMPLE + @( $true, [int16]1, [int]2, [long]3, [uint16]4, [uint32]5, [uint64]6, [float]7.8, [double]9.0) | ConvertTo-CBase64 + + Demonstrates that `ConvertTo-CBase64` can convert booleans, all sizes of signed and unsigned ints, floats, and + doubles to base64. + #> + [CmdletBinding()] + [OutputType([String])] + param( + [Parameter(Mandatory,ValueFromPipeline,Position=0)] + [AllowNull()] + [AllowEmptyString()] + # The value to base64 encode. + [Object]$InputObject, + + # The encoding to use. Default is Unicode/UTF-16 (the default .NET encoding for strings). This parameter is only + # used if encoding a string or char array. + [Text.Encoding]$Encoding = ([Text.Encoding]::Unicode) + ) + + begin + { + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + $collector = $null + $collectingBytes = $false + $collectingChars = $false + $collecting = $false + $stopProcessing = $false + $inspectedFirstItem = $false + } + + process + { + if( $stopProcessing ) + { + return + } + + if( $null -eq $InputObject ) + { + return + } + + if( $InputObject -is [Collections.IEnumerable] -and $InputObject -isnot [String] ) + { + Write-Debug "$($InputObject.GetType().FullName)" + $InputObject | ConvertTo-CBase64 -Encoding $Encoding + return + } + + $isByte = $InputObject -is [byte] + $isChar = $InputObject -is [char] + + if( $PSCmdlet.MyInvocation.ExpectingInput -and -not $inspectedFirstItem ) + { + $inspectedFirstItem = $true + + if( $isByte ) + { + $collecting = $true + $collectingBytes = $true + $collector = [Collections.Generic.List[byte]]::New() + Write-Debug -Message ("Collecting bytes.") + } + elseif( $isChar ) + { + $collecting = $true + $collectingChars = $true + $collector = [Collections.Generic.List[char]]::New() + Write-Debug -Message ("Collecting chars.") + } + } + + if( $collecting ) + { + # Looks like we didn't get passed an array of bytes or chars, but an array of mixed object types. + if( (-not $isByte -and $collectingBytes) -or (-not $isChar -and $collectingChars) ) + { + $collecting = $false + + # Since we are no longer collecting, we need to encode all the previous items we collected. + foreach( $item in $collector ) + { + ConvertTo-CBase64 -InputObject $item -Encoding $Encoding + } + ConvertTo-CBase64 -InputObject $InputObject -Encoding $Encoding + return + } + + [void]$collector.Add($InputObject) + return + } + + if( $InputObject -is [String] ) + { + return [Convert]::ToBase64String($Encoding.GetBytes($InputObject)) + } + + if( $isByte ) + { + return [Convert]::ToBase64String([byte[]]$InputObject) + } + + if( $InputObject -is [bool] -or $isChar -or $InputObject -is [int16] -or $InputObject -is [int] -or ` + $InputObject -is [long] -or $InputObject -is [uint16] -or $InputObject -is [uint32] -or ` + $InputObject -is [uint64] -or $InputObject -is [float] -or $InputObject -is [double] ) + { + return [Convert]::ToBase64String([BitConverter]::GetBytes($InputObject)) + } + + $stopProcessing = $true + $msg = "Failed to base64 encode ""$($InputObject.GetType().FullName)"" object. The " + + 'ConvertTo-CBase64 function can only convert strings, chars, bytes, bools, all signed and unsigned ' + + 'integers, floats, and doubles.' + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + } + + end + { + if( $stopProcessing ) + { + return + } + + if( -not $collecting ) + { + return + } + + if( $collectingChars ) + { + $bytes = $Encoding.GetBytes($collector.ToArray()) + } + elseif( $collectingBytes ) + { + $bytes = $collector.ToArray() + } + + [Convert]::ToBase64String($bytes) + } +} diff --git a/Carbon.Core/Functions/Get-CPowerShellPath.ps1 b/Carbon.Core/Functions/Get-CPowerShellPath.ps1 new file mode 100644 index 0000000..dc86f05 --- /dev/null +++ b/Carbon.Core/Functions/Get-CPowerShellPath.ps1 @@ -0,0 +1,105 @@ + +function Get-CPowershellPath +{ + <# + .SYNOPSIS + Gets the path to powershell.exe. + + .DESCRIPTION + Returns the path to the powershell.exe binary for the machine's default architecture (i.e. x86 or x64). If you're + on a x64 machine and want to get the path to x86 PowerShell, set the `x86` switch. + + Here are the possible combinations of operating system, PowerShell, and desired path architectures, and the path + they map to. + + +-----+-----+------+--------------------------------------------------------------+ + | OS | PS | Path | Result | + +-----+-----+------+--------------------------------------------------------------+ + | x64 | x64 | x64 | $env:windir\System32\Windows PowerShell\v1.0\powershell.exe | + | x64 | x64 | x86 | $env:windir\SysWOW64\Windows PowerShell\v1.0\powershell.exe | + | x64 | x86 | x64 | $env:windir\sysnative\Windows PowerShell\v1.0\powershell.exe | + | x64 | x86 | x86 | $env:windir\SysWOW64\Windows PowerShell\v1.0\powershell.exe | + | x86 | x86 | x64 | $env:windir\System32\Windows PowerShell\v1.0\powershell.exe | + | x86 | x86 | x86 | $env:windir\System32\Windows PowerShell\v1.0\powershell.exe | + +-----+-----+------+--------------------------------------------------------------+ + + .EXAMPLE + Get-CPowerShellPath + + Returns the path to the version of PowerShell that matches the computer's architecture (i.e. x86 or x64). + + .EXAMPLE + Get-CPowerShellPath -x86 + + Returns the path to the x86 version of PowerShell. Only valid on Windows. + #> + [CmdletBinding()] + param( + # The architecture of the PowerShell executable to run. The default is the architecture of the current + # process. + [switch]$x86 + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + Write-Debug "[Carbon\Get-CPowerShellPath]" + + # Map the system directory name from the current PowerShell architecture to the requested architecture. + $sysDirNames = @{ + # If PowerShell is 64-bit + 'x64' = @{ + # These are paths to PowerShell matching requested architecture. + 'x64' = 'System32'; + 'x86' = 'SysWOW64'; + }; + # If PowerShell is 32-bit. + 'x86' = @{ + # These are the paths to get to the appropriate architecture. + 'x64' = 'sysnative'; + 'x86' = 'System32'; + } + } + + $executableName = 'powershell.exe' + $edition = 'Desktop' + if( (Test-CPowerShell -IsCore) ) + { + $edition = 'Core' + $executableName = 'pwsh' + if( (Test-COperatingSystem -IsWindows) ) + { + $executableName = "$($executableName).exe" + } + } + Write-Debug -Message " Edition $($edition)" + + # PowerShell is always in the same place on x86 Windows. + $osArchitecture = 'x64' + if( (Test-COperatingSystem -Is32Bit) ) + { + $osArchitecture = 'x32' + return Join-Path -Path $PSHOME -ChildPath $executableName + } + Write-Debug -Message " Operating System Architecture $($osArchitecture)" + + $architecture = 'x64' + if( $x86 ) + { + $architecture = 'x86' + } + + $psArchitecture = 'x64' + if( (Test-CPowerShell -Is32Bit) ) + { + $psArchitecture = 'x86' + } + + Write-Debug -Message " PowerShell Architecture $($psArchitecture)" + Write-Debug -Message " Requested Architecture $($architecture)" + $sysDirName = $sysDirNames[$psArchitecture][$architecture] + Write-Debug -Message " Architecture SysDirName $($sysDirName)" + + $path = $PSHOME -replace '\b(System32|SysWOW64)\b', $sysDirName + return Join-Path -Path $path -ChildPath $executableName +} diff --git a/Carbon.Core/Functions/Invoke-CPowerShell.ps1 b/Carbon.Core/Functions/Invoke-CPowerShell.ps1 new file mode 100644 index 0000000..0375874 --- /dev/null +++ b/Carbon.Core/Functions/Invoke-CPowerShell.ps1 @@ -0,0 +1,154 @@ + +function Invoke-CPowerShell +{ + <# + .SYNOPSIS + Invokes a new `powershell.exe` process. + + .DESCRIPTION + The `Invoke-CPowerShell` scripts executes a new PowerShell process. Pass the parameters to pass to the executable + to the ArgumentList parameter. The function uses the `&` operator to run PowerShell. By default, the PowerShell + executable in `$PSHOME` is used. In Windows PowerShell (i.e. powershell.exe), the PowerShell executable in "$PSHOME" + that matches the architecture of the operating system. Use the `x86` switch to use 32-bit `powershell.exe`. Because + this function uses the `&` operator to execute PowerShell, all the PowerShell streams from the invoked command are + returned (e.g. stdout, verbose, warning, error, stderr, etc.). + + To use a different PowerShell executable, like PowerShell Core (i.e. pwsh), pass the path to the PowerShell + executable to the `Path` parameter. If the PowerShell executable is in your PATH, you can pass just the executable + name. + + If you want to run an encoded command, pass it to the `Command` parameter. The value of the Command parameter will + be base64 encoded and added to the end of the arguments in the ArgumentList parameter, along with the + "-EncodedCommand" switch. + + You can run the PowerShell process as a different user by passing that user's credentials to the `Credential` + parameter. `Invoke-CPowerShell` uses the Start-Job cmdlet to start a background job with those credentials. + `Start-Job` runs PowerShell with the `&` operator. + + There is a known issue on Linux and macOS that prevents the `Start-Job` cmdlet (what `Invoke-CPowerShell` uses to + run PowerShell as another user) from starting PowerShell as another user. See + https://github.com/PowerShell/PowerShell/issues/7172 for more information. + + .EXAMPLE + Invoke-CPowerShell -ArgumentList '-NoProfile','-NonInteractive','-Command','$PID' + + Demonstrates how to start a new PowerShell process. + + .EXAMPLE + Invoke-CPowerShell -Command $aLargePSScript -ArgumentList '-NoProfile','-NonInteractive' + + Demonstrates how to run an encoded command. In this example, `Invoke-CPowerShell` encodes the command in the + `Command` parameter, then runs PowerShell with `-NoProfile -NonInteractive -EncodedCommand $encodedCommand` + parameters. + + .EXAMPLE + Invoke-CPowerShell -Credential $cred -ArgumentList '-NoProfile','-NonInteractive','-Command','[Environment]::UserName' + + Demonstrates how to run PowerShell as a different user by passing that user's credentials to the `Credential` + parameter. This credential is passed to the `Start-Job` cmdlet's `Credential` parameter, then PowerShell is + executed using the `&` operator. + + .EXAMPLE + Invoke-CPowerShell -x86 -ArgumentList '-Command','[Environment]::Is64BitProcess' + + Demonstrates how to run PowerShell in a 32-bit process. This switch only has an effect on 64-bit Windows operating + systems. On other systems, use the `-Path` parameter to run PowerShell with a different architecture (which must + be installed). + + .EXAMPLE + Invoke-CPowerShell -Path 'pwsh' -ArgumentList '-Command','$PSVersionTable.Edition' + + Demonstrates how to use a custom PowerShell executable. In this case the first `pwsh` command found in your PATH + environment variable is used. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + # Any arguments to pass to PowerShell. They are passed as-is, so you'll need to handle any necessary + # escaping. + # + # If you need to run an encoded command, use the `Command` parameter to pass the command and this parameter to + # pass other parameters. The encoded command will be added to the end of the arguments. + [Object[]]$ArgumentList, + + # The command to run, as a string. The command will be base64 encoded first and passed to PowerShell's + # `EncodedCommand` parameter. + [String]$Command, + + # Run PowerShell as a specific user. Pass that user's credentials. + # + # There is a known issue on Linux and macOS that prevents the `Start-Job` cmdlet (what `Invoke-CPowerShell` + # uses to run PowerShell as another user) from starting PowerShell as another user. See + # https://github.com/PowerShell/PowerShell/issues/7172 for more information. + [pscredential]$Credential, + + # Run the x86 (32-bit) version of PowerShell. If not provided, the version which matches the OS architecture + # is used, *regardless of the architecture of the currently running process*. I.e. this command is run under + # a 32-bit PowerShell on a 64-bit operating system, without this switch, `Invoke-CPowerShell` will start a + # 64-bit "PowerShell". + # + # This switch is only used on Windows. + [switch]$x86, + + # The path to the PowerShell executable to use. The default is to use the executable in "$PSHOME". On Windows, + # the PowerShell executable in the "$PSHOME" that matches the operating system's architecture is used. + # + # If the PowerShell executable is in your `PATH`, you can pass the executable name instead. + [String]$Path + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + if( -not $Path ) + { + $params = @{ } + if( $x86 ) + { + $params.x86 = $true + } + + $Path = Get-CPowerShellPath @params + } + + $ArgumentList = & { + if( $ArgumentList ) + { + $ArgumentList | Write-Output + } + + if( $Command ) + { + '-EncodedCommand' | Write-Output + $Command | ConvertTo-CBase64 | Write-Output + } + } + + Write-Verbose -Message $Path + $ArgumentList | ForEach-Object { Write-Verbose -Message " $($_)" } + if( $Credential ) + { + $location = Get-Location + $currentDir = [Environment]::CurrentDirectory + $output = $null + $WhatIfPreference = $false + Start-Job -Credential $Credential -ScriptBlock { + Set-Location $using:location + [Environment]::CurrentDirectory = $using:currentDir + & $using:Path $using:ArgumentList + $LASTEXITCODE + exit $LASTEXITCODE + } | + Receive-Job -Wait -AutoRemoveJob | + Tee-Object 'output' | + Select-Object -SkipLast 1 + + $LASTEXITCODE = $output | Select-Object -Last 1 + } + else + { + & $Path $ArgumentList + } + Write-Verbose -Message " LASTEXITCODE $($LASTEXITCODE)" +} + diff --git a/Carbon.Core/Functions/Test-COperatingSystem.ps1 b/Carbon.Core/Functions/Test-COperatingSystem.ps1 new file mode 100644 index 0000000..3edf212 --- /dev/null +++ b/Carbon.Core/Functions/Test-COperatingSystem.ps1 @@ -0,0 +1,95 @@ + +function Test-COperatingSystem +{ + <# + .SYNOPSIS + Tests attributes of the current operating system. + + .DESCRIPTION + The `Test-COperatingSystem` function tests atrributes of the current operating system, returning `$true` if they + are `$true` and `$false` otherwise. It supports the following switches (only one can be given at at time) that + return the following attributes: + + * `Is32Bit`: is the architecture 32-bit? Uses `[Environment]::Is64BitOperatingSystem`. + * `Is64Bit`: is the architecture 64-bit? Uses `[Environment]::Is64BitOperatingSystem`. + * `IsWindows`: is the operating system Windows? Uses the `$IsWindows` built-in variable, it it exists. If it doesn't, + returns `$true` (only Windows operating systems don't have this variable). + * `IsLinux`: is the operating system Linux? Uses the `$IsLinux` built-in variable, if it exists. If it doesn't, + returns `$false` (all Linux systems have the `IsLinux` variable). + * `IsMacOS`: is the operating system macOS? Uses the `$IsMacOS` built-in variable, if it exists. If it doesn't, + returns `$false` (all macOS systems have the `IsMacOS` variable). + + + .OUTPUTS + System.Boolean. + + .LINK + http://msdn.microsoft.com/en-us/library/system.environment.is64bitoperatingsystem.aspx + + .EXAMPLE + Test-COperatingSystem -Is32Bit + + Demonstrates how to test if the current operating system is 32-bit/x86. + + .EXAMPLE + Test-COperatingSystem -Is64Bit + + Demonstrates how to test if the current operating system is 64-bit/x64. + + .EXAMPLE + Test-COperatingSystem -IsWindows + + Demonstrates how to test if the current operating system is Windows. + + .EXAMPLE + Test-COperatingSystem -IsLinux + + Demonstrates how to test if the current operating system is Linux. + + .EXAMPLE + Test-COperatingSystem -IsMacOS + + Demonstrates how to test if the current operating system is macOS. + + #> + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory,ParameterSetName='Is32Bit')] + [switch]$Is32Bit, + + [Parameter(Mandatory,ParameterSetName='Is64Bit')] + [switch]$Is64Bit, + + [Parameter(Mandatory,ParameterSetName='IsWindows')] + [Alias('IsWindows')] + [switch]$Windows, + + [Parameter(Mandatory,ParameterSetName='IsLinux')] + [Alias('IsLinux')] + [switch]$Linux, + + [Parameter(Mandatory,ParameterSetName='IsMacOS')] + [Alias('IsMacOS')] + [switch]$MacOS + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + switch( $PSCmdlet.ParameterSetName ) + { + 'Is32Bit' { return -not [Environment]::Is64BitOperatingSystem } + 'Is64Bit' { return [Environment]::Is64BitOperatingSystem } + 'IsWindows' { + if( (Test-Path -Path 'variable:IsWindows') ) + { + return $IsWindows + } + return $true + } + 'IsLinux' { return (Test-Path -Path 'variable:IsLinux') -and $IsLinux } + 'IsMacOS' { return (Test-Path -Path 'variable:IsMacOS') -and $IsMacOS } + } +} + diff --git a/Carbon.Core/Functions/Test-CPowerShell.ps1 b/Carbon.Core/Functions/Test-CPowerShell.ps1 new file mode 100644 index 0000000..638729b --- /dev/null +++ b/Carbon.Core/Functions/Test-CPowerShell.ps1 @@ -0,0 +1,67 @@ + +function Test-CPowerShell +{ + <# + .SYNOPSIS + Tests attributes of the current PowerShell process. + + .DESCRIPTION + The `Test-CPowerShell` function tests attributes of the current PowerShell process (or process hosting the + current PowerShell runspace). It uses the following switches to test the following conditions: + + * `Is32Bit`: if the process architecture is 32-bit/x86 (uses `[Environment]::Is64BitProcess`). + * `Is64Bit`: if the process architecture is 64-bit/x64 (uses `[Environment]::Is64BitProcess`). + * `IsDesktop`: if the process is running on Windows PowerShell (uses `$PSVersionTable.Edition`; if this property + doesn't exist, always returns `$true`). + * `IsCore`: if the process is running PowerShell Core (uses `$PSVersionTable.Edition`). + + .OUTPUTS + System.Boolean. + + .EXAMPLE + Test-CPowerShell -Is32Bit + + Demonstrates how to test if the current PowerShell process architecture is 32-bit/x86. + + .EXAMPLE + Test-CPowerShell -Is64Bit + + Demonstrates how to test if the current PowerShell process architecture is 64-bit/x64. + + .EXAMPLE + Test-CPowerShell -IsDesktop + + Demonstrates how to test if the current PowerShell process is Windows PowerShell. + + .EXAMPLE + Test-CPowerShell -IsCore + + Demonstrates how to test if the current PowerShell process is Windows Core. + #> + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory,ParameterSetName='Is32Bit')] + [switch]$Is32Bit, + + [Parameter(Mandatory,ParameterSetName='Is64Bit')] + [switch]$Is64Bit, + + [Parameter(Mandatory,ParameterSetName='IsDesktop')] + [switch]$IsDesktop, + + [Parameter(Mandatory,ParameterSetName='IsCore')] + [switch]$IsCore + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + switch( $PSCmdlet.ParameterSetName ) + { + 'Is32Bit' { return -not [Environment]::Is64BitProcess } + 'Is64Bit' { return [Environment]::Is64BitProcess } + 'IsDesktop' { return -not $PSVersionTable['PSEdition'] -or $PSVersionTable['PSEdition'] -eq 'Desktop' } + 'IsCore' { return $PSVersionTable['PSEdition'] -eq 'Core' } + } +} diff --git a/Carbon.Management/Functions/Use-CallerPreference.ps1 b/Carbon.Core/Functions/Use-CallerPreference.ps1 similarity index 100% rename from Carbon.Management/Functions/Use-CallerPreference.ps1 rename to Carbon.Core/Functions/Use-CallerPreference.ps1 diff --git a/Carbon.Core/Import-Carbon.Core.ps1 b/Carbon.Core/Import-Carbon.Core.ps1 new file mode 100644 index 0000000..f79a702 --- /dev/null +++ b/Carbon.Core/Import-Carbon.Core.ps1 @@ -0,0 +1,53 @@ +# Copyright Aaron Jensen and WebMD Health Services +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +<# +.SYNOPSIS +Imports the Carbon.Core module into the current session. + +.DESCRIPTION +The `Import-Carbon.Core function imports the Carbon.Core module into the current session. If the module is already loaded, it is removed, then reloaded. + +.EXAMPLE +.\Import-Carbon.Core.ps1 + +Demonstrates how to use this script to import the Carbon.Core module into the current PowerShell session. +#> +[CmdletBinding()] +param( +) + +#Requires -Version 5.1 +Set-StrictMode -Version 'Latest' + +$originalVerbosePref = $Global:VerbosePreference +$originalWhatIfPref = $Global:WhatIfPreference + +$Global:VerbosePreference = $VerbosePreference = 'SilentlyContinue' +$Global:WhatIfPreference = $WhatIfPreference = $false + +try +{ + if( (Get-Module -Name 'Carbon.Core') ) + { + Remove-Module -Name 'Carbon.Core' -Force + } + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'Carbon.Core.psd1' -Resolve) +} +finally +{ + $Global:VerbosePreference = $originalVerbosePref + $Global:WhatIfPreference = $originalWhatIfPref +} diff --git a/Carbon.Core/en-US/about_Carbon.Core.help.txt b/Carbon.Core/en-US/about_Carbon.Core.help.txt new file mode 100644 index 0000000..574d80a --- /dev/null +++ b/Carbon.Core/en-US/about_Carbon.Core.help.txt @@ -0,0 +1,45 @@ +TOPIC + about_Carbon.Core + +SHORT DESCRIPTION + Carbon.Core is a PowerShell module that contains functions that makes writing PowerShell automation easier. + +LONG DESCRIPTION + + # Overview + + The Carbon.Core PowerShell module contains functions that help makes writing PowerShell automation easier. It + contains the following functions: + + * ConvertTo-CBase64: Convert strings, characters, bytes, numbers, and booleans into base64 strings. + * Get-CPowerShellPath: Get the path to the PowerShell executable. + * Invoke-CPowerShell: Run a PowerShell executable, as the current user or another. On 64-bit Windows, you can also + run 32-bit or 64-bit PowerShell processes. + * Test-COperatingSystem: Test if the current operating system is 32-bit, 64-bit, Windows, Linux, or macOS. + * Test-CPowerShell: Test if the current PowerShell instance is 32-bit, 64-bit, Core edition, or Desktop edition. + + # System Requirements + + * Windows PowerShell 5.1+ and .NET 4.6.1+ + * PowerShell Core 6+ + + # Installing + + `Carbon.Core` is only available on the PowerShell Gallery. To install globally: + + Install-Module -Name 'Carbon.Core' + Import-Module -Name 'Carbon.Core' + + To install privately, + + Save-Module -Name 'Carbon.Core' -Path '.' + Import-Module -Name '.\Carbon.Core' + + ZIP packages are also available on the + [project's site on GitHub](https://github.com/webmd-health-services/Carbon.Core). + + # Links + + * [Project](https://github.com/webmd-health-services/Carbon.Core) + * [Carbon.Core on the PowerShell Gallery](https://www.powershellgallery.com/packages/Carbon.Core) + \ No newline at end of file diff --git a/Carbon.Management/Carbon.Management.psd1 b/Carbon.Management/Carbon.Management.psd1 deleted file mode 100644 index 23d1230..0000000 --- a/Carbon.Management/Carbon.Management.psd1 +++ /dev/null @@ -1,120 +0,0 @@ -# -# Module manifest for module '<$- moduleName ' -# -# Generated on: Tue Jan 19 2021 18:06:22 GMT-0800 (Pacific Standard Time) -# - -@{ - - # Script module or binary module file associated with this manifest. - RootModule = 'Carbon.Management.psm1' - - # Version number of this module. - ModuleVersion = '0.0.0' - - # ID used to uniquely identify this module - GUID = '20DA9F42-23C4-4917-8597-DCFD7EE4AD00' - - # Author of this module - Author = 'WebMD Health Services' - - # Company or vendor of this module - CompanyName = 'WebMD Health Services' - - # If you want to support .NET Core, add 'Core' to this list. - CompatiblePSEditions = @( 'Desktop' ) - - # Copyright statement for this module - Copyright = '(c) 2021 WebMD Health Services. All rights reserved.' - - # Description of the functionality provided by this module - Description = 'Core Carbon functions that are used by other Carbon modules.' - - # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.1' - - # Name of the Windows PowerShell host required by this module - # PowerShellHostName = '' - - # Minimum version of the Windows PowerShell host required by this module - # PowerShellHostVersion = '' - - # Minimum version of Microsoft .NET Framework required by this module - # DotNetFrameworkVersion = '' - - # Minimum version of the common language runtime (CLR) required by this module - # CLRVersion = '' - - # Processor architecture (None, X86, Amd64) required by this module - # ProcessorArchitecture = '' - - # Modules that must be imported into the global environment prior to importing this module - # RequiredModules = @() - - # Assemblies that must be loaded prior to importing this module - # RequiredAssemblies = @( ) - - # Script files (.ps1) that are run in the caller's environment prior to importing this module. - # ScriptsToProcess = @() - - # Type files (.ps1xml) to be loaded when importing this module - # TypesToProcess = @() - - # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @( ) - - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = @() - - # Functions to export from this module. Only list public function here. - FunctionsToExport = @( - ) - - # Cmdlets to export from this module. By default, you get a script module, so there are no cmdlets. - # CmdletsToExport = @() - - # Variables to export from this module. Don't export variables except in RARE instances. - VariablesToExport = @() - - # Aliases to export from this module. Don't create/export aliases. It can pollute your user's sessions. - AliasesToExport = @() - - # DSC resources to export from this module - # DscResourcesToExport = @() - - # List of all modules packaged with this module - # ModuleList = @() - - # List of all files packaged with this module - # FileList = @() - - # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @( ) - - # A URL to the license for this module. - #LicenseUri = '' - - # A URL to the main website for this project. - ProjectUri = 'https://whsbitbucket.webmd.net/projects/POWERSHELL/repos/Carbon.Management/browse' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - ReleaseNotes = @' -'@ - } # End of PSData hashtable - - } # End of PrivateData hashtable - - # HelpInfo URI of this module - # HelpInfoURI = '' - - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' -} diff --git a/Carbon.Management/Functions/FunctionTemplate.ps1 b/Carbon.Management/Functions/FunctionTemplate.ps1 deleted file mode 100644 index fbbdddf..0000000 --- a/Carbon.Management/Functions/FunctionTemplate.ps1 +++ /dev/null @@ -1,26 +0,0 @@ - -# Use approved PowerShell verbs for all functions/scripts: https://docs.microsoft.com/en-us/powershell/developer/cmdlet/approved-verbs-for-windows-powershell-commands -# Use singular nouns, not plural, e.g. `Get-Thing` *not* `Get-Things`. -# See https://whsconfluence.webmd.net/display/WHS/PowerShell+Coding+Standards for our PowerShell Coding Standards -function FUNCTION_NAME -{ - <# - .SYNOPSIS - ... - - .DESCRIPTION - The `FUNCTION_NAME` function... - - .EXAMPLE - - This example demonstrates... - #> - [CmdletBinding()] - param( - - ) - - Set-StrictMode -Version 'Latest' - Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState - -} \ No newline at end of file diff --git a/Carbon.Management/Import-Carbon.Management.ps1 b/Carbon.Management/Import-Carbon.Management.ps1 deleted file mode 100644 index 7771938..0000000 --- a/Carbon.Management/Import-Carbon.Management.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -<# -.SYNOPSIS -Imports the Carbon.Management module into the current session. - -.DESCRIPTION -The `Import-Carbon.Management function imports the Carbon.Management module into the current session. If the module is already loaded, it is removed, then reloaded. - -.EXAMPLE -.\Import-Carbon.Management.ps1 - -Demonstrates how to use this script to import the Carbon.Management module into the current PowerShell session. -#> -[CmdletBinding()] -param( -) - -#Requires -Version 5.1 -Set-StrictMode -Version 'Latest' - -$originalVerbosePref = $Global:VerbosePreference -$originalWhatIfPref = $Global:WhatIfPreference - -$Global:VerbosePreference = $VerbosePreference = 'SilentlyContinue' -$Global:WhatIfPreference = $WhatIfPreference = $false - -try -{ - if( (Get-Module -Name 'Carbon.Management') ) - { - Remove-Module -Name 'Carbon.Management' -Force - } - - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'Carbon.Management.psd1' -Resolve) -} -finally -{ - $Global:VerbosePreference = $originalVerbosePref - $Global:WhatIfPreference = $originalWhatIfPref -} diff --git a/Carbon.Management/en-US/about_Carbon.Management.help.txt b/Carbon.Management/en-US/about_Carbon.Management.help.txt deleted file mode 100644 index 6ce808b..0000000 --- a/Carbon.Management/en-US/about_Carbon.Management.help.txt +++ /dev/null @@ -1,9 +0,0 @@ -TOPIC - about_Carbon.Management - -SHORT DESCRIPTION - Carbon.Management is a PowerShell module for... - -LONG DESCRIPTION - The Carbon.Management PowerShell module... - diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4b2dc92 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright Aaron Jensen and WebMD Health Services + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..b168ca7 --- /dev/null +++ b/NOTICE @@ -0,0 +1,3 @@ +Carbon.Core + +Copyright WebMD Health Services. \ No newline at end of file diff --git a/README.md b/README.md index 8a822f7..630512a 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,45 @@ # Overview -The `Carbon.Management` module contains general-purpose functions that make doing things in PowerShell much +The `Carbon.Core` module contains general-purpose functions that make doing things in PowerShell much easier. It is also the core dependency for other modules in the Carbon family. -* `Invoke-CPowerShell`: a function for executing a PowerShell process, optionally under different .NET runtimes and - as a different user. +This module exports the following functions: + +* `ConvertTo-CBase64`: convert things to base64 encoded strings. +* `Invoke-CPowerShell`: a function for executing a PowerShell process, optionally a different edition/executable, +different architecture, and/or as a different user. +* `Test-COperatingSystem`: check if the current operating system is 32-bit, 64-bit, Windows, Linux, or macOS. +* `Test-CPowerShell`: check if the current PowerShell instance is 32-bit, 64-bit, Core edition, or Desktop edition +(older versions of PowerShell that don't specify their edition are assumed to be Desktop). + # System Requirements -* PowerShell 5.1+ +* Windows PowerShell 5.1+ and .NET 4.6.1+ +* PowerShell Core 6+ # Installing To install globally: ```powershell -Install-Module -Name 'Carbon.Management' -Import-Module -Name 'Carbon.Management' +Install-Module -Name 'Carbon.Core' +Import-Module -Name 'Carbon.Core' ``` -To install to a custom, private location: +To install privately: ```powershell -Save-Module -Name 'Carbon.Management' -Path '.' -Import-Module -Name '.\Carbon.Management' +Save-Module -Name 'Carbon.Core' -Path '.' +Import-Module -Name '.\Carbon.Core' ``` # The Carbon Family -* `Carbon`: the original. This is where all the other Carbon modules started from. It is great and one of the most +* [Carbon](http://get-carbon.org): the original. This is where all the other Carbon modules started from. It is great and one of the most downloaded modules on the PowerShell Gallery. It also has *a lot* of functionality that you may or may not use. All this functionality makes it hard to maintain. Functions in here will slowly migrate to other modules as those functions need to be updated. -* `Carbon.Management` (this module): This module contains the core, general-purpose functions. This module will - frequently be a dependency of other Carbon modules. -* `Carbon.Cryptography`: functions for encrypting and decrypting strings and creating RSA public/private keys for - encrypting/decrypting. - +* **Carbon.Core** (this module): This module contains the core, general-purpose functions that are used by other +Carbon modules. +* [Carbon.Cryptography](https://github.com/WebMD-Health-Services/Carbon.Cryptography): functions for encrypting and decrypting strings and creating RSA public/private keys for encrypting/decrypting. diff --git a/Tests/Carbon.Management.Tests.ps1 b/Tests/Carbon.Core.Tests.ps1 similarity index 73% rename from Tests/Carbon.Management.Tests.ps1 rename to Tests/Carbon.Core.Tests.ps1 index 769a14f..66f4c6f 100644 --- a/Tests/Carbon.Management.Tests.ps1 +++ b/Tests/Carbon.Core.Tests.ps1 @@ -3,11 +3,11 @@ #Requires -Version 5.1 Set-StrictMode -Version 'Latest' -& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Carbon.ManagementTest.ps1' -Resolve) +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) function GivenModuleImported { - # Don't do anything since Initialize-Carbon.Management.ps1 imports the module. + # Don't do anything since Initialize-Test.ps1 imports the module. } function Init @@ -66,15 +66,15 @@ function ThenHelpTopic } } -Describe ('Carbon.Management.help topic') { +Describe ('Carbon.Core.help topic') { It 'should have one' { Init GivenModuleImported - ThenHelpTopic 'about_Carbon.Management' -Exists + ThenHelpTopic 'about_Carbon.Core' -Exists } } -Describe ('Carbon.Management.command verbs') { +Describe ('Carbon.Core.command verbs') { It 'should only use approved verbs' { Init GivenModuleImported @@ -82,11 +82,11 @@ Describe ('Carbon.Management.command verbs') { } } -Describe ('Carbon.Management.command help topics') { +Describe ('Carbon.Core.command help topics') { It 'should have a help topic for each command' { Init GivenModuleImported - foreach( $cmd in (Get-Command -Module 'Carbon.Management' -CommandType Function,Cmdlet,Filter)) + foreach( $cmd in (Get-Command -Module 'Carbon.Core' -CommandType Function,Cmdlet,Filter)) { ThenHelpTopic $cmd.Name -Exists -HasSynopsis -HasDescription -HasExamples } diff --git a/Tests/Carbon.CoreTestHelper/Carbon.CoreTestHelper.psm1 b/Tests/Carbon.CoreTestHelper/Carbon.CoreTestHelper.psm1 new file mode 100644 index 0000000..935c0cc --- /dev/null +++ b/Tests/Carbon.CoreTestHelper/Carbon.CoreTestHelper.psm1 @@ -0,0 +1,11 @@ + +function Get-TestUser +{ + param( + [Parameter(Mandatory)] + [String]$Name + ) + + $password = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath '..\.password' -Resolve) -TotalCount 1 + return [pscredential]::New($Name, (ConvertTo-SecureString -String $password -AsPlainText -Force)) +} diff --git a/Tests/Carbon.ManagementTestHelper/Carbon.ManagementTestHelper.psm1 b/Tests/Carbon.ManagementTestHelper/Carbon.ManagementTestHelper.psm1 deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/ConvertTo-CBase64.Tests.ps1 b/Tests/ConvertTo-CBase64.Tests.ps1 new file mode 100644 index 0000000..e63bc82 --- /dev/null +++ b/Tests/ConvertTo-CBase64.Tests.ps1 @@ -0,0 +1,228 @@ + +#Requires -Version 5.1 +Set-StrictMode -Version 'Latest' + +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) + +Describe 'ConvertTo-CBase64.when piped strings' { + It 'should base64 all the strings' { + $one, $two, $three = 'one', 'two', 'three' | ConvertTo-CBase64 + $one | Should -Be 'bwBuAGUA' + $two | Should -Be 'dAB3AG8A' + $three | Should -Be 'dABoAHIAZQBlAA==' + } +} + +Describe 'ConvertTo-CBase64.when piped chars' { + It 'should base64 encode all the chars together' { + $result = 'four'.ToCharArray() | ConvertTo-CBase64 + $result | Should -Be 'ZgBvAHUAcgA=' + } +} + +Describe 'ConvertTo-CBase64.when piped bytes' { + It 'should base64 encode all the bytes together' { + $result = [Text.Encoding]::Unicode.GetBytes('five') | ConvertTo-CBase64 + $result | Should -Be 'ZgBpAHYAZQA=' + } +} + +Describe 'ConvertTo-CBase64.when passed array of objects' { + It 'should convert all the objects' { + $seven, $eight, $nine = ConvertTo-CBase64 'seven', ('eight'.ToCharArray()), ([Text.Encoding]::Unicode.GetBytes('nine')) + $seven | Should -Be 'cwBlAHYAZQBuAA==' + $eight | Should -Be 'ZQBpAGcAaAB0AA==' + $nine | Should -Be 'bgBpAG4AZQA=' + } +} + +Describe 'ConvertTo-CBase64.when passed array of strings' { + It 'should convert each string' { + $ten, $eleven, $twelve = ConvertTo-CBase64 ([String[]]@('ten', 'eleven', 'twelve')) + $ten | Should -Be 'dABlAG4A' + $eleven | Should -Be 'ZQBsAGUAdgBlAG4A' + $twelve | Should -Be 'dAB3AGUAbAB2AGUA' + } +} + +Describe 'ConvertTo-CBase64.when passed array of chars' { + It 'should convert the whole array' { + $chars = 'thirteen'.ToCharArray() + $chars -is [char[]] | Should -BeTrue + $thirteen = ConvertTo-CBase64 $chars + $thirteen | Should -Be 'dABoAGkAcgB0AGUAZQBuAA==' + } +} + +Describe 'ConvertTo-CBase64.when passed array of bytes' { + It 'should convert the whole array' { + $bytes = [Text.Encoding]::Unicode.GetBytes('fourteen') + $bytes -is [byte[]] | Should -BeTrue + $fourteen = ConvertTo-CBase64 $bytes + $fourteen | Should -Be 'ZgBvAHUAcgB0AGUAZQBuAA==' + } +} + +Describe 'ConvertTo-CBase64.when passed $null' { + It 'should do nothing' { + $null | ConvertTo-CBase64 | Should -HaveCount 0 + } +} + +Describe 'ConvertTo-CBase64.when piping chars mixed with non-chars' { + It 'should encode each item' { + $chars1 = 'fi'.ToCharArray() + $bytes = [Text.Encoding]::Unicode.GetBytes('fte') + $chars2 = 'en'.ToCharArray() + $result = & { + $chars1 + $bytes + $chars2 + } | ConvertTo-CBase64 + $result | Should -HaveCount ($chars1.Length + $bytes.Length + $chars2.Length) + $idx = 0 + $result[$idx++] | Should -Be 'ZgA=' + $result[$idx++] | Should -Be 'aQA=' + $result[$idx++] | Should -Be 'Zg==' + $result[$idx++] | Should -Be 'AA==' + $result[$idx++] | Should -Be 'dA==' + $result[$idx++] | Should -Be 'AA==' + $result[$idx++] | Should -Be 'ZQ==' + $result[$idx++] | Should -Be 'AA==' + $result[$idx++] | Should -Be 'ZQA=' + $result[$idx++] | Should -Be 'bgA=' + } +} + +Describe 'ConvertTo-CBase64.when piping bytes mixed with non-bytes' { + It 'should encode each item' { + $bytes1 = [Text.Encoding]::Unicode.GetBytes('si') + $chars = 'xte'.ToCharArray() + $bytes2 = [Text.Encoding]::Unicode.GetBytes('en') + & { + $bytes1 + $chars + $bytes2 + } | ConvertTo-CBase64 | Should -HaveCount ($bytes1.Length + $chars.Length + $bytes2.Length) + } +} + +Describe 'ConvertTo.CBase64.when piping types convertable to bytes' { + It 'should convert each object' { + $result = & { + $true + $false + + [char]'a' + + [Int16]::MinValue + [Int16]0 + [Int16]::MaxValue + + [int]::MinValue + [int]0 + [int]::MaxValue + + [Int64]::MinValue + [Int64]0 + [Int64]::MaxValue + + [UInt16]::MinValue + [UInt16]::MaxValue + + [UInt32]::MinValue + [UInt32]::MaxValue + + [UInt64]::MinValue + [UInt64]::MaxValue + + [float]::MinValue + [float]::Epsilon + [float]::MaxValue + [float]::NaN + [float]::NegativeInfinity + [float]::PositiveInfinity + + [double]::MinValue + [double]::Epsilon + [double]::MaxValue + [double]::NaN + [double]::NegativeInfinity + [double]::PositiveInfinity + + } | ConvertTo-CBase64 + + $idx = 0 + + # bool + $result[$idx++] | Should -Be 'AQ==' + $result[$idx++] | Should -Be 'AA==' + + # char + $result[$idx++] | Should -Be 'YQA=' + + # Int16/short + $result[$idx++] | Should -Be 'AIA=' + $result[$idx++] | Should -Be 'AAA=' + $result[$idx++] | Should -Be '/38=' + + # Int32/int + $result[$idx++] | Should -Be 'AAAAgA==' + $result[$idx++] | Should -Be 'AAAAAA==' + $result[$idx++] | Should -Be '////fw==' + + # Int64/long + $result[$idx++] | Should -Be 'AAAAAAAAAIA=' + $result[$idx++] | Should -Be 'AAAAAAAAAAA=' + $result[$idx++] | Should -Be '/////////38=' + + # UInt16/ushort + $result[$idx++] | Should -Be 'AAA=' + $result[$idx++] | Should -Be '//8=' + + # UInt32/uint + $result[$idx++] | Should -Be 'AAAAAA==' + $result[$idx++] | Should -Be '/////w==' + + # UInt64/ulong + $result[$idx++] | Should -Be 'AAAAAAAAAAA=' + $result[$idx++] | Should -Be '//////////8=' + + # float + $result[$idx++] | Should -Be '//9//w==' + $result[$idx++] | Should -Be 'AQAAAA==' + $result[$idx++] | Should -Be '//9/fw==' + $result[$idx++] | Should -Be 'AADA/w==' + $result[$idx++] | Should -Be 'AACA/w==' + $result[$idx++] | Should -Be 'AACAfw==' + + # double + $result[$idx++] | Should -Be '////////7/8=' + $result[$idx++] | Should -Be 'AQAAAAAAAAA=' + $result[$idx++] | Should -Be '////////738=' + $result[$idx++] | Should -Be 'AAAAAAAA+P8=' + $result[$idx++] | Should -Be 'AAAAAAAA8P8=' + $result[$idx++] | Should -Be 'AAAAAAAA8H8=' + + } +} + +Describe 'ConvertTo-CBase64.when piping an invalid object' { + It 'should fail' { + $Global:Error.Clear() + $result = [pscustomobject]@{} | ConvertTo-CBase64 -ErrorAction SilentlyContinue + $result | Should -BeNullOrEmpty + $result | Should -HaveCount 0 + $Global:Error | Should -HaveCount 1 + $Global:Error | Should -Match 'Failed to base64 encode "System.Management.Automation.PSCustomObject" object' + } +} + +Describe 'ConvertTo-CBase64.when using a custom encoding' { + It 'should encode with bytes from that encoding' { + $defaultResult = 'seventeen' | ConvertTo-CBase64 + $result = 'seventeen' | ConvertTo-CBase64 -Encoding ([Text.Encoding]::ASCII) + $result | Should -Not -Be $defaultResult -Because 'ASCII and Unicode encoding results in different bytes' + $result | Should -Be 'c2V2ZW50ZWVu' + } +} \ No newline at end of file diff --git a/Tests/Get-CPowerShellPath.Tests.ps1 b/Tests/Get-CPowerShellPath.Tests.ps1 new file mode 100644 index 0000000..6d6dfbb --- /dev/null +++ b/Tests/Get-CPowerShellPath.Tests.ps1 @@ -0,0 +1,104 @@ + +#Requires -Version 5.1 +Set-StrictMode -Version 'Latest' +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) + +function GivenOSIs32Bit +{ + Mock -CommandName 'Test-COperatingSystem' ` + -ModuleName 'Carbon.Core' ` + -ParameterFilter { $Is32Bit } ` + -MockWith { $true } +} + +function GivenOSIs64Bit +{ + Mock -CommandName 'Test-COperatingSystem' ` + -ModuleName 'Carbon.Core' ` + -ParameterFilter { $Is64Bit } ` + -MockWith { $true } +} + +function GivenPowerShellIs32Bit +{ + Mock -CommandName 'Test-CPowerShell' ` + -ModuleName 'Carbon.Core' ` + -ParameterFilter { $Is32Bit } ` + -MockWith { $true } +} + +function GivenPowerShellIs64Bit +{ + Mock -CommandName 'Test-CPowerShell' ` + -ModuleName 'Carbon.Core' ` + -ParameterFilter { $Is64Bit } ` + -MockWith { $true } +} + +Describe 'Get-CPowerShellPath' { + if( (Test-COperatingSystem -IsWindows) ) + { + Context 'Windows' { + if( (Test-CPowerShell -IsDesktop) ) + { + Context 'PowerShell Desktop' { + $system32Path = Join-Path -Path $env:windir -ChildPath 'System32\WindowsPowerShell\v1.0\powershell.exe' + $sysnativePath = Join-Path -Path $env:windir -ChildPath 'sysnative\WindowsPowerShell\v1.0\powershell.exe' + $sysWowPath = Join-Path -Path $env:windir -ChildPath 'SysWOW64\WindowsPowerShell\v1.0\powershell.exe' + Context 'OS x86' { + GivenOSIs32Bit + It 'should return path in System32' { + Get-CPowerShellPath | Should -Be $system32Path + } + } + + Context 'OS x64' { + GivenOSIs64Bit + Context 'PowerShell x64' { + GivenPowerShellIs64Bit + Context 'requesting default PowerShell' { + It 'should return path in System32' { + Get-CPowerShellPath | Should -Be $system32Path + } + } + Context 'requesting x86 PowerShell' { + It 'should return SysWOW64 path' { + Get-CPowerShellPath -x86 | Should -Be $sysWowPath + } + } + } + Context 'PowerShell x86' { + GivenPowerShellIs32Bit + Context 'requesting default PowerShell' { + It 'should return path in sysnative' { + Get-CPowerShellPath | Should -Be $sysnativePath + } + } + Context 'requesting x86 PowerShell' { + It 'should return System32 path' { + Get-CPowerShellPath -x86 | Should -Be $system32Path + } + } + } + } + } + } + else + { + Context 'PowerShell Core' { + It 'should return pwsh.exe from PSHOME' { + Get-CPowerShellPath | Should -Be (Join-Path -Path $PSHOME -ChildPath 'pwsh.exe') + } + } + } + } + } + else + { + Context 'Linux/macOS' { + It 'should return pwsh in PSHOME' { + Get-CPowerShellPath | Should -Be (Join-Path -Path $PSHOME -ChildPath 'pwsh') + } + } + } +} \ No newline at end of file diff --git a/Tests/Import-Carbon.Management.Tests.ps1 b/Tests/Import-Carbon.Core.Tests.ps1 similarity index 51% rename from Tests/Import-Carbon.Management.Tests.ps1 rename to Tests/Import-Carbon.Core.Tests.ps1 index 3bac9a9..5f0f1c4 100644 --- a/Tests/Import-Carbon.Management.Tests.ps1 +++ b/Tests/Import-Carbon.Core.Tests.ps1 @@ -2,17 +2,17 @@ #Requires -Version 5.1 Set-StrictMode -Version 'Latest' -& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Carbon.ManagementTest.ps1' -Resolve) +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) function GivenModuleLoaded { - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Management\Carbon.Management.psd1' -Resolve) - Get-Module -Name 'Carbon.Management' | Add-Member -MemberType NoteProperty -Name 'NotReloaded' -Value $true + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Core\Carbon.Core.psd1' -Resolve) + Get-Module -Name 'Carbon.Core' | Add-Member -MemberType NoteProperty -Name 'NotReloaded' -Value $true } function GivenModuleNotLoaded { - Remove-Module -Name 'Carbon.Management' -Force -ErrorAction Ignore + Remove-Module -Name 'Carbon.Core' -Force -ErrorAction Ignore } function Init @@ -22,7 +22,7 @@ function Init function ThenModuleLoaded { - $module = Get-Module -Name 'Carbon.Management' + $module = Get-Module -Name 'Carbon.Core' $module | Should -Not -BeNullOrEmpty $module | Get-Member -Name 'NotReloaded' | Should -BeNullOrEmpty } @@ -31,10 +31,10 @@ function WhenImporting { $script:importedAt = Get-Date Start-Sleep -Milliseconds 1 - & (Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Management\Import-Carbon.Management.ps1' -Resolve) + & (Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Core\Import-Carbon.Core.ps1' -Resolve) } -Describe 'Import-Carbon.Management.when module not loaded' { +Describe 'Import-Carbon.Core.when module not loaded' { It 'should import the module' { Init GivenModuleNotLoaded @@ -43,7 +43,7 @@ Describe 'Import-Carbon.Management.when module not loaded' { } } -Describe 'Import-Carbon.Management.when module loaded' { +Describe 'Import-Carbon.Core.when module loaded' { It 'should re-import the module' { Init GivenModuleLoaded diff --git a/Tests/Initialize-Carbon.ManagementTest.ps1 b/Tests/Initialize-Test.ps1 similarity index 80% rename from Tests/Initialize-Carbon.ManagementTest.ps1 rename to Tests/Initialize-Test.ps1 index e1d5177..d8acd6f 100644 --- a/Tests/Initialize-Carbon.ManagementTest.ps1 +++ b/Tests/Initialize-Test.ps1 @@ -3,7 +3,7 @@ Gets things ready for your tests to run. .DESCRIPTION -The `Initialize-Carbon.ManagementTest.ps1` script gets your tests ready to run by: +The `Initialize-Test.ps1` script gets your tests ready to run by: * Importing the module you're testing. * Importing your test helper module. @@ -14,7 +14,7 @@ Execute this script as the first thing in each of your test fixtures: #Requires -Version 5.1 Set-StrictMode -Version 'Latest' - & (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Carbon.ManagementTest.ps1' -Resolve) + & (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) #> [CmdletBinding()] param( @@ -29,8 +29,8 @@ $Global:WhatIfPreference = $WhatIfPreference = $false try { $modules = [ordered]@{ - 'Carbon.Management' = '..\Carbon.Management'; - 'Carbon.ManagementTestHelper' = 'Carbon.ManagementTestHelper'; + 'Carbon.Core' = '..\Carbon.Core'; + 'Carbon.CoreTestHelper' = 'Carbon.CoreTestHelper'; } foreach( $moduleName in $modules.Keys ) { diff --git a/Tests/Invoke-CPowerShell.Tests.ps1 b/Tests/Invoke-CPowerShell.Tests.ps1 new file mode 100644 index 0000000..7481dbd --- /dev/null +++ b/Tests/Invoke-CPowerShell.Tests.ps1 @@ -0,0 +1,175 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Requires -Version 5.1 +Set-StrictMode -Version 'Latest' + +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) + +$testCredential = Get-TestUser -Name 'CIPowerShell' + +function Init +{ + $Global:Error.Clear() + $script:output = $null +} + +function ThenOutputIs +{ + param( + [Object]$InputObject + ) + + $output | Should -Be $InputObject +} + +function WhenRunningPowerShell +{ + param( + [String[]]$WithArgs, + + [String]$Command, + + [switch]$As32Bit, + + [pscredential]$As + ) + + # Make tests a little faster and reduce test code clutter. + $WithArgs = & { + '-NoProfile' + '-NonInteractive' + if( $WithArgs ) + { + $WithArgs + } + } + + $conditionalParams = @{} + if( $Command ) + { + $conditionalParams['Command'] = $Command + } + + if( $As32Bit ) + { + $conditionalParams['x86'] = $true + } + + if( $As ) + { + $conditionalParams['Credential'] = $As + } + + $script:output = Invoke-CPowerShell -ArgumentList $WithArgs @conditionalParams +} + +function Assert-EnvVarCleanedUp +{ + It 'should clean up environment' { + ([Environment]::GetEnvironmentVariable('COMPLUS_ApplicationMigrationRuntimeActivationConfigPath')) | Should -BeNullOrEmpty + } +} + +Describe 'Invoke-CPowerShell.when requesting a 32-bit PowerShell' { + if( (Test-COperatingSystem -IsWindows) -and (Test-CPowerShell -IsDesktop) ) + { + Context 'On Windows' { + It 'should run under x86' { + Init + WhenRunningPowerShell -WithArgs '-Command', '"[Environment]::Is64BitProcess"' -As32Bit + ThenOutputIs $false.ToString() + } + } + } + else + { + Context 'On PowerShell Core' { + It 'should run under same architecture as this test' { + Init + WhenRunningPowerShell -WithArgs '-Command', '"[Environment]::Is64BitProcess"' -As32Bit + ThenOutputIs ([Environment]::Is64BitProcess) + } + } + } +} + +if( (Test-COperatingSystem -IsWindows) -and (Test-CPowerShell -IsDesktop) ) +{ + Describe 'Invoke-CPowerShell.when running on Windows' { + if( (Test-COperatingSystem -Is64Bit) ) + { + Context 'x64' { + Context 'from x86 PowerShell' { + It 'should run x64 PowerShell by default' { + Init + if( (Test-CPowerShell -Is32Bit) ) + { + WhenRunningPowerShell -WithArgs '-Command', '"[Environment]::Is64BitProcess"' + } + else + { + $command = @" +Import-Module "$(Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Core' -Resolve)" +if( -not (Test-CPowerShell -Is32Bit) ) +{ + throw 'Not in 32-bit PowerShell!' +} +Invoke-CPowerShell -ArgumentList '-NoProfile', '-NonInteractive', '[Environment]::Is64BitProcess' +"@ + WhenRunningPowerShell -Command $command -As32Bit + } + ThenOutputIs $true + } + } + Context 'from x64 PowerShell' { + It 'should run x64 PowerShell' { + Init + if( (Test-CPowerShell -Is64Bit) ) + { + WhenRunningPowerShell -WithArgs '-Command', '"[Environment]::Is64BitProcess"' + } + else + { + $command = @" +Import-Module "$(Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Core' -Resolve)" +if( -not (Test-CPowerShell -Is64Bit) ) +{ + throw 'Not in 64-bit PowerShell!' +} +return [Environment]::Is64BitPowerShell +"@ + WhenRunningPowerShell -Command $command + } + ThenOutputIs $true + } + } + } + } + } +} + +# On macOS/Linux, there's a bug in Start-Job that prevents it from launching as another user. +# https://github.com/PowerShell/PowerShell/issues/7172 +$skip = @{} +if( -not (Test-COperatingSystem -IsWindows) ) +{ + $skip['Skip'] = $true +} + +Describe 'Invoke-CPowerShell.when running a script as another user' { + It 'should run PowerShell as that user' @skip { + Init + WhenRunningPowerShell -WithArgs '-Command', '"[Environment]::UserName"' -As $testCredential + ThenOutputIs $testCredential.UserName + } +} diff --git a/Tests/Test-COperatingSystem.Tests.ps1 b/Tests/Test-COperatingSystem.Tests.ps1 new file mode 100644 index 0000000..e1e662c --- /dev/null +++ b/Tests/Test-COperatingSystem.Tests.ps1 @@ -0,0 +1,110 @@ + +#Requires -Version 5.1 +Set-StrictMode -Version 'Latest' + +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) + +$onWindows = $true +$onLinux = $false +$onMac = $false + +if( (Test-Path -Path 'variable:IsWindows') ) +{ + $onWindows = $IsWindows + $onLinux = $IsLinux + $onMac = $IsMacOS +} + +Describe 'Test-COperatingSystem' { + if( [Environment]::Is64BitOperatingSystem ) + { + Context 'OS x64' { + Context 'when testing if OS is x64' { + It 'should return $true' { + Test-COperatingSystem -Is64Bit | Should -BeTrue + } + } + Context 'when testing if OS is x86' { + It 'should return $false' { + Test-COperatingSystem -Is32Bit | Should -BeFalse + } + } + } + } + else + { + Context 'OS x86' { + Context 'when testing if OS is x64' { + It 'should return $false' { + Test-COperatingSystem -Is64Bit | Should -BeFalse + } + } + Context 'when testing if OS is x86' { + It 'should return $true' { + Test-COperatingSystem -Is32Bit | Should -BeTrue + } + } + } + } + + if( $onWindows ) + { + Context 'Windows' { + Context 'when testing if OS is Linux' { + It 'should return $false' { + Test-COperatingSystem -IsLinux | Should -BeFalse + } + } + Context 'when testing if OS is macOS' { + It 'should return $false' { + Test-COperatingSystem -IsMacOS | Should -BeFalse + } + } + Context 'when testing if OS is Windows' { + It 'should return $true' { + Test-COperatingSystem -IsWindows | Should -BeTrue + } + } + } + } + elseif( $onLinux ) + { + Context 'Linux' { + Context 'when testing if OS is Linux' { + It 'should return $true' { + Test-COperatingSystem -IsLinux | Should -BeTrue + } + } + Context 'when testing if OS is macOS' { + It 'should return $false' { + Test-COperatingSystem -IsMacOS | Should -BeFalse + } + } + Context 'when testing if OS is Windows' { + It 'should return $False' { + Test-COperatingSystem -IsWindows | Should -BeFalse + } + } + } + } + elseif( $onMac ) + { + Context 'macOS' { + Context 'when testing if OS is Linux' { + It 'should return $false' { + Test-COperatingSystem -IsLinux | Should -BeFalse + } + } + Context 'when testing if OS is macOS' { + It 'should return $true' { + Test-COperatingSystem -IsMacOS | Should -BeTrue + } + } + Context 'when testing if OS is Windows' { + It 'should return $false' { + Test-COperatingSystem -IsWindows | Should -BeFalse + } + } + } + } +} \ No newline at end of file diff --git a/Tests/Test-CPowerShell.Tests.ps1 b/Tests/Test-CPowerShell.Tests.ps1 new file mode 100644 index 0000000..7092a48 --- /dev/null +++ b/Tests/Test-CPowerShell.Tests.ps1 @@ -0,0 +1,70 @@ + +#Requires -Version 5.1 +Set-StrictMode -Version 'Latest' + +& (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) + +Describe 'Test-CPowerShell' { + if( [Environment]::Is64BitProcess ) + { + Context 'x64' { + Context 'when testing if PowerShell is x64' { + It 'should return $true' { + Test-CPowerShell -Is64Bit | Should -BeTrue + } + } + Context 'when testing if PowerShell is x86' { + It 'should return $false' { + Test-CPowerShell -Is32Bit | Should -BeFalse + } + } + } + } + else + { + Context 'x86' { + Context 'when testing if PowerShell is x64' { + It 'should return $false' { + Test-CPowerShell -Is64Bit | Should -BeFalse + } + } + Context 'when testing if PowerShell is x86' { + It 'should return $true' { + Test-CPowerShell -Is32Bit | Should -BeTrue + } + } + } + } + + if( -not $PSVersionTable['PSEdition'] -or $PSVersionTable['PSEdition'] -eq 'Desktop' ) + { + Context 'Desktop' { + Context 'when testing if PowerShell is Desktop edition' { + It 'should return $true' { + Test-CPowerShell -IsDesktop | Should -BeTrue + } + } + Context 'when testing if PowerShell is Core edition' { + It 'should return $false' { + Test-CPowerShell -IsCore | Should -BeFalse + } + } + } + } + else + { + Context 'Core' { + Context 'when testing if PowerShell is Desktop edition' { + It 'should return $false' { + Test-CPowerShell -IsDesktop | Should -BeFalse + } + } + Context 'when testing if PowerShell is Core edition' { + It 'should return $true' { + Test-CPowerShell -IsCore | Should -BeTrue + } + } + } + + } +} \ No newline at end of file diff --git a/Tests/users.psd1 b/Tests/users.psd1 new file mode 100644 index 0000000..aa7adae --- /dev/null +++ b/Tests/users.psd1 @@ -0,0 +1,8 @@ +@{ + Users = @( + @{ + 'Name' = 'CIPowerShell'; + 'For' = 'Invoke-CPowerShell'; + } + ) +} \ No newline at end of file diff --git a/init.ps1 b/init.ps1 new file mode 100644 index 0000000..4a6acbd --- /dev/null +++ b/init.ps1 @@ -0,0 +1,94 @@ +[CmdletBinding()] +param( +) + +#Requires -RunAsAdministrator +Set-StrictMode -Version 'Latest' + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'PSModules\Carbon' -Resolve) + +if( -not (Test-Path -Path 'variable:IsWindows') )Carbon.Core +{ + $IsWindows = $true + $IsLinux = $false + $IsMacOS = $false +} + +$passwordPath = Join-Path -Path $PSScriptRoot -ChildPath 'Tests\.password' +if( -not (Test-Path -Path $passwordPath) ) +{ + $rng = [Security.Cryptography.RNGCryptoServiceProvider]::New() + $randomBytes = [byte[]]::New(9) + do + { + $rng.GetBytes($randomBytes); + $password = [Convert]::ToBase64String($randomBytes) + } + # Password needs to contain uppercase letter, lowercase letter, and a number. + while( $password -cnotmatch '[A-Z]' -and $password -cnotmatch '[a-z]' -and $password -notmatch '\d' ) + $password | Set-Content -Path $passwordPath + + $randomBytes = [byte[]]::New(6) + $rng.GetBytes($randomBytes) + $salt = [Convert]::ToBase64String($randomBytes) + $salt | Add-Content -Path $passwordPath +} + +$password,$salt = Get-Content -Path $passwordPath -TotalCount 2 +$users = + Import-LocalizedData -BaseDirectory (Join-Path -Path $PSScriptRoot -ChildPath 'Tests') -FileName 'users.psd1' | + ForEach-Object { $_['Users'] } | + ForEach-Object { + $_['Description'] = "Carbon.Core $($_['For']) test user." + [pscustomobject]$_ | Write-Output + } + +foreach( $user in $users ) +{ + if( $IsWindows ) + { + $maxLength = $user.Description.Length + if( $maxLength -gt 48 ) + { + $maxLength = 48 + } + $description = $user.Description.Substring(0, $maxLength) + $credential = [pscredential]::New($user.Name, (ConvertTo-SecureString $password -AsPlainText -Force)) + Install-CUser -Credential $credential -Description $description -UserCannotChangePassword + } + elseif( $IsMacOS ) + { + $newUid = + sudo dscl . -list /Users UniqueID | + ForEach-Object { $username,$uid = $_ -split ' +' ; return [int]$uid } | + Sort-Object | + Select-Object -Last 1 + Write-Verbose " Found highest user ID ""$($newUid)""." -Verbose + $newUid += 1 + + $username = $user.Name + + Write-Verbose " Creating $($username) (uid: $($newUid))" -Verbose + # Create the user account + sudo dscl . -create /Users/$username + sudo dscl . -create /Users/$username UserShell /bin/bash + sudo dscl . -create /Users/$username RealName $username + sudo dscl . -create /Users/$username UniqueID $newUid + sudo dscl . -create /Users/$username PrimaryGroupID 20 + sudo dscl . -create /Users/$username NFSHomeDirectory /Users/$username + sudo dscl . -passwd /Users/$username $password + sudo createhomedir -c + } + elseif( $IsLinux ) + { + $userExists = Get-Content '/etc/passwd' | Where-Object { $_ -match "^$([regex]::Escape($user.Name))\b"} + if( $userExists ) + { + continue + } + + Write-Verbose -Message ("Adding user ""$($user.Name)"".") + $encryptedPassword = $password | openssl passwd -stdin -salt $salt + sudo useradd -p $encryptedPassword -m $user.Name --comment $user.Description + } +} \ No newline at end of file diff --git a/reset.ps1 b/reset.ps1 new file mode 100644 index 0000000..19d9951 --- /dev/null +++ b/reset.ps1 @@ -0,0 +1,39 @@ +[CmdletBinding()] +param( +) + +#Requires -RunAsAdministrator +Set-StrictMode -Version 'Latest' + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'PSModules\Carbon' -Resolve) + +if( -not (Test-Path -Path 'variable:IsWindows') ) +{ + $IsWindows = $true + $IsLinux = $false + $IsMacOS = $false +} + +$usernames = + Import-LocalizedData -BaseDirectory (Join-Path -Path $PSScriptRoot -ChildPath 'Tests') -FileName 'users.psd1' | + ForEach-Object { $_['Users'] } | + ForEach-Object { $_['Name'] } + +foreach( $username in $usernames ) +{ + if( $IsWindows ) + { + Uninstall-CUser -UserName $username + } + else + { + Write-Verbose -Message "Deleting user ""$($username)""." + sudo userdel -r -f $username + } +} + +$passwordPath = Join-Path -Path $PSScriptRoot -ChildPath 'Tests\.password' +if( (Test-Path -Path $passwordPath) ) +{ + Remove-Item -Path $passwordPath -Force +} diff --git a/whiskey.yml b/whiskey.yml index 8d1aad4..9477e9d 100644 --- a/whiskey.yml +++ b/whiskey.yml @@ -5,34 +5,50 @@ PublishOn: Build: - Version: - Path: Carbon.Management\Carbon.Management.psd1 + Path: Carbon.Core\Carbon.Core.psd1 # Dot-sourcing files is expensive. Move all functions into your .psm1 file to # improve import speed. Do this before testing to ensure your module still # works. - MergeFile: OnlyBy: BuildServer Path: - - Carbon.Management\Functions\*.ps1 - DestinationPath: Carbon.Management\Carbon.Management.psm1 + - Carbon.Core\Functions\*.ps1 + DestinationPath: Carbon.Core\Carbon.Core.psm1 DeleteSourceFiles: true TextSeparator: "$(NewLine)$(NewLine)" +- CopyFile: + OnlyBy: BuildServer + Path: + - LICENSE + - NOTICE + DestinationDirectory: Carbon.Core +# Needed to install test users. +- GetPowerShellModule: + Name: Carbon + Version: 2.* +- PowerShell: + ExceptDuring: Clean + Path: init.ps1 +- PowerShell: + OnlyDuring: Clean + Path: reset.ps1 - Pester4: - Path: Tests\*.Tests.ps1 + Script: Tests\*.Tests.ps1 - Zip: - ArchivePath: .output\Carbon.Management.zip + ArchivePath: .output\Carbon.Core.zip Path: - - Carbon.Management + - Carbon.Core Publish: - PublishPowerShellModule: UnlessExists: env:APPVEYOR_PULL_REQUEST_NUMBER RepositoryName: PSGallery RepositoryUri: https://powershellgallery.com/api/v2/ - Path: Carbon.Management + Path: Carbon.Core ApiKeyID: PowerShellGallery - SetVariableFromPowerShellDataFile: - Path: Carbon.Management\Carbon.Management.psd1 + Path: Carbon.Core\Carbon.Core.psd1 Variables: PrivateData: PSData: @@ -40,13 +56,13 @@ Publish: - GitHubRelease: UnlessExists: env:APPVEYOR_PULL_REQUEST_NUMBER - RepositoryName: webmd-health-services/Carbon.Management + RepositoryName: webmd-health-services/Carbon.Core ApiKeyID: github.com Tag: $(WHISKEY_SEMVER2_NO_BUILD_METADATA) Commitish: $(WHISKEY_SCM_COMMIT_ID) Name: $(WHISKEY_SEMVER2_NO_BUILD_METADATA) Description: $(RELEASE_NOTES) Assets: - - Path: .output\Carbon.Management.zip + - Path: .output\Carbon.Core.zip ContentType: application/zip - Name: Carbon.Management-$(WHISKEY_SEMVER2_NO_BUILD_METADATA).zip \ No newline at end of file + Name: Carbon.Core-$(WHISKEY_SEMVER2_NO_BUILD_METADATA).zip \ No newline at end of file From e36622fa35d093f7b8cf9505bd7272beb95bbc98 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Mon, 25 Jan 2021 19:34:49 -0800 Subject: [PATCH 2/3] Make sure the right people are added as reviewers to pull requests. --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..eafc3bb --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# These owners will be the default owners for everything in +# the repo. The owners will be requested for review when +# someone opens a pull request. +* @webmd-health-services/devops \ No newline at end of file From 630dd49216101c6aa1450d3f6b2fa5d9c83f2f32 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Fri, 22 Jan 2021 17:45:07 -0800 Subject: [PATCH 3/3] Getting builds working. --- Tests/.password | 2 -- appveyor.yml | 39 +++++++++++++++++++++++++++++++++++++++ build.ps1 | 15 +++++++++++++++ init.ps1 | 26 ++++++++++++++------------ whiskey.yml | 14 ++++++++++++-- 5 files changed, 80 insertions(+), 16 deletions(-) delete mode 100644 Tests/.password create mode 100644 appveyor.yml diff --git a/Tests/.password b/Tests/.password deleted file mode 100644 index a1650c3..0000000 --- a/Tests/.password +++ /dev/null @@ -1,2 +0,0 @@ -0AsNzX6rA3bi -IbycsagY diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..bbafa68 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,39 @@ +version: 0.0.{build} + +skip_tags: true + +skip_branch_with_pr: true + +image: +- Visual Studio 2015 +- Visual Studio 2017 +- Visual Studio 2019 +- Ubuntu +- macOS + +build: + verbosity: minimal + +build_script: +- pwsh: .\build.ps1 -Verbose + +for: +- matrix: + only: + - image: Visual Studio 2015 + - image: Visual Studio 2017 + build_script: + - ps: .\build.ps1 -Verbose + - pwsh: .\build.ps1 -Verbose + +- matrix: + only: + - image: Ubuntu + environment: + LANG: us_US.UTF-8 + +- matrix: + only: + - image: Visual Studio 2019 + environment: + PUBLISH: True diff --git a/build.ps1 b/build.ps1 index a6b6869..cd97f1f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -155,4 +155,19 @@ if( $Initialize ) } $context = New-WhiskeyContext -Environment 'Dev' -ConfigurationPath $configPath +$apiKeys = @{ + 'AppVeyorBearerToken' = 'WHS_APPVEYOR_BEARER_TOKEN'; + 'GitHubAccessToken' = 'WHS_GITHUB_ACCESS_TOKEN'; + 'PowerShellGalleryApiKey' = 'WHS_POWERSHELL_GALLERY_API_KEY'; +} +foreach( $apiKeyName in $apiKeys.Keys ) +{ + $envVarName = $apiKeys[$apiKeyName] + $path = "env:$($envVarName)" + if( -not (Test-Path -Path $path) ) + { + continue + } + Add-WhiskeyApiKey -Context $context -ID $apiKeyName -Value (Get-Item -Path $path).Value +} Invoke-WhiskeyBuild -Context $context @optionalArgs diff --git a/init.ps1 b/init.ps1 index 4a6acbd..32d6742 100644 --- a/init.ps1 +++ b/init.ps1 @@ -5,13 +5,9 @@ param( #Requires -RunAsAdministrator Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'PSModules\Carbon' -Resolve) - -if( -not (Test-Path -Path 'variable:IsWindows') )Carbon.Core -{ - $IsWindows = $true - $IsLinux = $false - $IsMacOS = $false +& { + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'PSModules\Carbon' -Resolve) -Verbose:$false + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'Carbon.Core' -Resolve) -Verbose:$false } $passwordPath = Join-Path -Path $PSScriptRoot -ChildPath 'Tests\.password' @@ -21,6 +17,7 @@ if( -not (Test-Path -Path $passwordPath) ) $randomBytes = [byte[]]::New(9) do { + Write-Verbose -Message ('Generating random password for test accounts.') $rng.GetBytes($randomBytes); $password = [Convert]::ToBase64String($randomBytes) } @@ -28,11 +25,16 @@ if( -not (Test-Path -Path $passwordPath) ) while( $password -cnotmatch '[A-Z]' -and $password -cnotmatch '[a-z]' -and $password -notmatch '\d' ) $password | Set-Content -Path $passwordPath + Write-Verbose -Message ('Generating IV for encrypting test account password on Linux.') $randomBytes = [byte[]]::New(6) $rng.GetBytes($randomBytes) $salt = [Convert]::ToBase64String($randomBytes) $salt | Add-Content -Path $passwordPath } +else +{ + Get-Content -Path $passwordPath -Raw | Write-Verbose +} $password,$salt = Get-Content -Path $passwordPath -TotalCount 2 $users = @@ -45,7 +47,7 @@ $users = foreach( $user in $users ) { - if( $IsWindows ) + if( (Test-COperatingSystem -IsWindows) ) { $maxLength = $user.Description.Length if( $maxLength -gt 48 ) @@ -56,19 +58,19 @@ foreach( $user in $users ) $credential = [pscredential]::New($user.Name, (ConvertTo-SecureString $password -AsPlainText -Force)) Install-CUser -Credential $credential -Description $description -UserCannotChangePassword } - elseif( $IsMacOS ) + elseif( (Test-COperatingSystem -IsMacOS) ) { $newUid = sudo dscl . -list /Users UniqueID | ForEach-Object { $username,$uid = $_ -split ' +' ; return [int]$uid } | Sort-Object | Select-Object -Last 1 - Write-Verbose " Found highest user ID ""$($newUid)""." -Verbose + Write-Verbose " Found highest user ID ""$($newUid)""." $newUid += 1 $username = $user.Name - Write-Verbose " Creating $($username) (uid: $($newUid))" -Verbose + Write-Verbose " Creating $($username) (uid: $($newUid))" # Create the user account sudo dscl . -create /Users/$username sudo dscl . -create /Users/$username UserShell /bin/bash @@ -79,7 +81,7 @@ foreach( $user in $users ) sudo dscl . -passwd /Users/$username $password sudo createhomedir -c } - elseif( $IsLinux ) + elseif( (Test-COperatingSystem -IsLinux) ) { $userExists = Get-Content '/etc/passwd' | Where-Object { $_ -match "^$([regex]::Escape($user.Name))\b"} if( $userExists ) diff --git a/whiskey.yml b/whiskey.yml index 9477e9d..ab955aa 100644 --- a/whiskey.yml +++ b/whiskey.yml @@ -11,6 +11,7 @@ Build: # works. - MergeFile: OnlyBy: BuildServer + IfExists: Carbon.Core\Functions\*.ps1 Path: - Carbon.Core\Functions\*.ps1 DestinationPath: Carbon.Core\Carbon.Core.psm1 @@ -34,18 +35,26 @@ Build: Path: reset.ps1 - Pester4: Script: Tests\*.Tests.ps1 +- Delete: + Path: .output\*.zip - Zip: ArchivePath: .output\Carbon.Core.zip Path: - Carbon.Core Publish: +- AppVeyorWaitForBuildJobs: + IfExists: env:PUBLISH + UnlessExists: env:APPVEYOR_PULL_REQUEST_NUMBER + ApiKeyID: AppVeyorBearerToken + - PublishPowerShellModule: + IfExists: env:PUBLISH UnlessExists: env:APPVEYOR_PULL_REQUEST_NUMBER RepositoryName: PSGallery RepositoryUri: https://powershellgallery.com/api/v2/ Path: Carbon.Core - ApiKeyID: PowerShellGallery + ApiKeyID: PowerShellGalleryApiKey - SetVariableFromPowerShellDataFile: Path: Carbon.Core\Carbon.Core.psd1 @@ -55,9 +64,10 @@ Publish: ReleaseNotes: RELEASE_NOTES - GitHubRelease: + IfExists: env:PUBLISH UnlessExists: env:APPVEYOR_PULL_REQUEST_NUMBER RepositoryName: webmd-health-services/Carbon.Core - ApiKeyID: github.com + ApiKeyID: GitHubAccessToken Tag: $(WHISKEY_SEMVER2_NO_BUILD_METADATA) Commitish: $(WHISKEY_SCM_COMMIT_ID) Name: $(WHISKEY_SEMVER2_NO_BUILD_METADATA)