├── .github └── FUNDING.yml ├── Build └── BuildModule.ps1 ├── CHANGELOG.MD ├── Examples ├── Example-DecryptFile.ps1 ├── Example-DecryptFolder.ps1 ├── Example-DecryptString.ps1 ├── Example-EncryptFolder.ps1 ├── Example-EncryptFolderMultipleKeys.ps1 ├── Example-EncryptString.ps1 ├── Example-EncryptStringMultipleKeys.ps1 ├── Example-EncryptStringNoPassword.ps1 ├── Example-GeneratePGP.ps1 ├── Example-VerifySignature.ps1 └── Test │ ├── Test1.txt │ ├── Test2.txt │ └── Test3.txt ├── License ├── PSPGP.AzurePipelines.yml ├── PSPGP.Tests.ps1 ├── PSPGP.psd1 ├── PSPGP.psm1 ├── Public ├── New-PGPKey.ps1 ├── Protect-PGP.ps1 ├── Test-PGP.ps1 └── Unprotect-PGP.ps1 ├── README.MD ├── Sources ├── PSPGP.sln └── PSPGP │ ├── Initialize.cs │ ├── OnImportAndRemove.cs │ └── PSPGP.csproj └── Tests └── PSPGP.Tests.ps1 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: PrzemyslawKlys 4 | custom: https://paypal.me/PrzemyslawKlys -------------------------------------------------------------------------------- /Build/BuildModule.ps1: -------------------------------------------------------------------------------- 1 | Build-Module -ModuleName 'PSPGP' { 2 | # Usual defaults as per standard module 3 | $Manifest = [ordered] @{ 4 | # Minimum version of the Windows PowerShell engine required by this module 5 | PowerShellVersion = '5.1' 6 | # prevent using over CORE/PS 7 7 | CompatiblePSEditions = @('Desktop', 'Core') 8 | # ID used to uniquely identify this module 9 | GUID = 'edbf6d52-2d66-405e-a4d4-d4a95db8fb45' 10 | # Version number of this module. 11 | ModuleVersion = '0.1.X' 12 | # Author of this module 13 | Author = 'Przemyslaw Klys' 14 | # Company or vendor of this module 15 | CompanyName = 'Evotec' 16 | # Copyright statement for this module 17 | Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved." 18 | # Description of the functionality provided by this module 19 | Description = 'PSPGP is a PowerShell module that provides PGP functionality in PowerShell. It allows encrypting and decrypting files/folders and strings using PGP.' 20 | # Tags applied to this module. These help with module discovery in online galleries. 21 | Tags = @('pgp', 'gpg', 'encrypt', 'decrypt', 'windows', 'macos', 'linux') 22 | # A URL to the main website for this project. 23 | ProjectUri = 'https://github.com/EvotecIT/PSPGP' 24 | 25 | IconUri = 'https://evotec.xyz/wp-content/uploads/2021/08/PSPGP.png' 26 | 27 | LicenseUri = 'https://github.com/EvotecIT/PSPGP/blob/master/License' 28 | DotNetFrameworkVersion = '4.7.2' 29 | } 30 | New-ConfigurationManifest @Manifest 31 | # Add external module dependencies, using loop for simplicity 32 | New-ConfigurationModule -Type ExternalModule -Name 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility' 33 | 34 | # Add approved modules, that can be used as a dependency, but only when specific function from those modules is used 35 | # And on that time only that function and dependant functions will be copied over 36 | # Keep in mind it has it's limits when "copying" functions such as it should not depend on DLLs or other external files 37 | New-ConfigurationModule -Type ApprovedModule -Name 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword' 38 | 39 | $ConfigurationFormat = [ordered] @{ 40 | RemoveComments = $false 41 | 42 | PlaceOpenBraceEnable = $true 43 | PlaceOpenBraceOnSameLine = $true 44 | PlaceOpenBraceNewLineAfter = $true 45 | PlaceOpenBraceIgnoreOneLineBlock = $true 46 | 47 | PlaceCloseBraceEnable = $true 48 | PlaceCloseBraceNewLineAfter = $false 49 | PlaceCloseBraceIgnoreOneLineBlock = $false 50 | PlaceCloseBraceNoEmptyLineBefore = $true 51 | 52 | UseConsistentIndentationEnable = $true 53 | UseConsistentIndentationKind = 'space' 54 | UseConsistentIndentationPipelineIndentation = 'IncreaseIndentationAfterEveryPipeline' 55 | UseConsistentIndentationIndentationSize = 4 56 | 57 | UseConsistentWhitespaceEnable = $true 58 | UseConsistentWhitespaceCheckInnerBrace = $true 59 | UseConsistentWhitespaceCheckOpenBrace = $true 60 | UseConsistentWhitespaceCheckOpenParen = $true 61 | UseConsistentWhitespaceCheckOperator = $true 62 | UseConsistentWhitespaceCheckPipe = $true 63 | UseConsistentWhitespaceCheckSeparator = $true 64 | 65 | AlignAssignmentStatementEnable = $true 66 | AlignAssignmentStatementCheckHashtable = $true 67 | 68 | UseCorrectCasingEnable = $true 69 | } 70 | # format PSD1 and PSM1 files when merging into a single file 71 | # enable formatting is not required as Configuration is provided 72 | New-ConfigurationFormat -ApplyTo 'OnMergePSM1', 'OnMergePSD1' -Sort None @ConfigurationFormat 73 | # format PSD1 and PSM1 files within the module 74 | # enable formatting is required to make sure that formatting is applied (with default settings) 75 | New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'DefaultPSM1' -Sort None @ConfigurationFormat 76 | # when creating PSD1 use special style without comments and with only required parameters 77 | New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'OnMergePSD1' -PSD1Style 'Minimal' 78 | 79 | # configuration for documentation, at the same time it enables documentation processing 80 | New-ConfigurationDocumentation -Enable:$false -StartClean -UpdateWhenNew -PathReadme 'Docs\Readme.md' -Path 'Docs' 81 | 82 | New-ConfigurationImportModule -ImportSelf #-ImportRequiredModules 83 | 84 | $newConfigurationBuildSplat = @{ 85 | Enable = $true 86 | # lets sign module only on my machine for now 87 | SignModule = if ($Env:COMPUTERNAME -eq 'EVOMONSTER') { $true } else { $false } 88 | MergeModuleOnBuild = $true 89 | MergeFunctionsFromApprovedModules = $true 90 | CertificateThumbprint = '483292C9E317AA13B07BB7A96AE9D1A5ED9E7703' 91 | #DotSourceLibraries = $false 92 | #DotSourceClasses = $false 93 | SeparateFileLibraries = $true 94 | #DeleteTargetModuleBeforeBuild = $true 95 | 96 | ResolveBinaryConflicts = $true 97 | ResolveBinaryConflictsName = 'PSPGP' 98 | NETProjectName = 'PSPGP' 99 | NETConfiguration = 'Release' 100 | NETFramework = 'netstandard2.0' 101 | #NETExcludeMainLibrary = $true 102 | NETExcludeLibraryFilter = @( 103 | 'System.Management.*.dll' 104 | ) 105 | DotSourceLibraries = $true 106 | DotSourceClasses = $true 107 | #SeparateFileLibraries = $true 108 | DeleteTargetModuleBeforeBuild = $true 109 | } 110 | 111 | New-ConfigurationBuild @newConfigurationBuildSplat #-DotSourceLibraries -DotSourceClasses -MergeModuleOnBuild -Enable -SignModule -DeleteTargetModuleBeforeBuild -CertificateThumbprint '483292C9E317AA13B07BB7A96AE9D1A5ED9E7703' -MergeFunctionsFromApprovedModules 112 | 113 | $newConfigurationArtefactSplat = @{ 114 | Type = 'Unpacked' 115 | Enable = $true 116 | Path = "$PSScriptRoot\..\Artefacts\Unpacked" 117 | ModulesPath = "$PSScriptRoot\..\Artefacts\Unpacked\Modules" 118 | RequiredModulesPath = "$PSScriptRoot\..\Artefacts\Unpacked\Modules" 119 | AddRequiredModules = $true 120 | CopyFiles = @{ 121 | #"Examples\PublishingExample\Example-ExchangeEssentials.ps1" = "RunMe.ps1" 122 | } 123 | } 124 | New-ConfigurationArtefact @newConfigurationArtefactSplat -CopyFilesRelative 125 | $newConfigurationArtefactSplat = @{ 126 | Type = 'Packed' 127 | Enable = $true 128 | Path = "$PSScriptRoot\..\Artefacts\Packed" 129 | ModulesPath = "$PSScriptRoot\..\Artefacts\Packed\Modules" 130 | RequiredModulesPath = "$PSScriptRoot\..\Artefacts\Packed\Modules" 131 | AddRequiredModules = $true 132 | CopyFiles = @{ 133 | #"Examples\PublishingExample\Example-ExchangeEssentials.ps1" = "RunMe.ps1" 134 | } 135 | ArtefactName = '.v.zip' 136 | } 137 | New-ConfigurationArtefact @newConfigurationArtefactSplat 138 | 139 | New-ConfigurationTest -TestsPath "$PSScriptRoot\..\Tests" -Enable 140 | 141 | # global options for publishing to github/psgallery 142 | #New-ConfigurationPublish -Type PowerShellGallery -FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' -Enabled:$true 143 | #New-ConfigurationPublish -Type GitHub -FilePath 'C:\Support\Important\GitHubAPI.txt' -UserName 'EvotecIT' -Enabled:$true 144 | } -ExitCode -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | ### ChangeLog 2 | 3 | #### 0.1.13 - 2024.09.18 4 | - Bump PgpCore version to 6.5.1 5 | - Small improvements 6 | - Update New-PGPKey.ps1 by @keteague in https://github.com/EvotecIT/PSPGP/pull/21 7 | 8 | ##### New Contributors 9 | * @keteague made their first contribution in https://github.com/EvotecIT/PSPGP/pull/21 10 | 11 | **Full Changelog**: https://github.com/EvotecIT/PSPGP/compare/v0.1.12...v0.1.13 12 | 13 | #### 0.1.12 - 2023.11.02 14 | - Improve resolving files on SMB shares and other non direct paths 15 | - Added HashAlgorithm to `New-PGPKey` to allow for different hash algorithms 16 | 17 | #### 0.1.11 - 2023.11.02 18 | - Added HashAlgorithm to `Protect-PGP` to allow for different hash algorithms 19 | - Tries to resolve issue with Az.KeyVault conficts 20 | 21 | #### 0.1.10 22 | - Fixes warning message [#8](https://github.com/EvotecIT/PSPGP/pull/8) - tnx s0up2up 23 | 24 | #### 0.1.9 - 2022.03.01 25 | - Fixes [Full Folder Decryption file path having incorrect property name](https://github.com/EvotecIT/PSPGP/issues/7) in `Write-Warning` 26 | 27 | #### 0.1.8 - 2022.03.01 28 | - Fixes [Protect-PGP does not create an encrypted file by specifying the path with -OutFilePath](https://github.com/EvotecIT/PSPGP/issues/6) 29 | 30 | #### 0.1.7 - 2022.02.17 31 | - Adds [ability to use multiple public keys to encrypt](https://github.com/EvotecIT/PSPGP/issues/2) 32 | - Fixes [Unprotect-PGP does not resolve path #3](https://github.com/EvotecIT/PSPGP/issues/3) 33 | - `Unprotect-PGP`/`Protect-PGP`/`Test-PGP` now resolve paths 34 | - `New-PGPKey`, `Unprotect-PGP` now [works without password](https://github.com/EvotecIT/PSPGP/issues/4) 35 | #### 0.1.6 - 2021.09.06 36 | - Fixes to publishing 37 | #### 0.1.5 - 2021.08.28 38 | - Improved error handling 39 | #### 0.1.4 - 2021.08.28 40 | - Updated library to higher version 41 | #### 0.1.3 - 2021.05.09 42 | - First release -------------------------------------------------------------------------------- /Examples/Example-DecryptFile.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | Protect-PGP -FilePathPublic "$PSScriptRoot\Keys\PublicPGP1.asc" -FilePath $PSScriptRoot\Test\Test1.txt -OutFilePath $PSScriptRoot\Encoded\Test1.txt.pgp 4 | Unprotect-PGP -FilePathPrivate "$PSScriptRoot\Keys\PrivatePGP1.asc" -Password 'ZielonaMila9!' -FilePath $PSScriptRoot\Encoded\Test1.txt.pgp -OutFilePath $PSScriptRoot\Decoded\Test4.txt 5 | 6 | #Protect-PGP -FilePathPublic "\\ad1\c$\Temp\PublicPGP1.asc" -FilePath $PSScriptRoot\Test\Test1.txt -OutFilePath $PSScriptRoot\Encoded\Test1.txt.pgp 7 | #Unprotect-PGP -FilePathPrivate '\\ad1\c$\Temp\PrivatePGP1.asc' -Password 'ZielonaMila9!' -FilePath $PSScriptRoot\Encoded\Test1.txt.pgp -OutFilePath $PSScriptRoot\Decoded\Test4.txt 8 | #Unprotect-PGP -FilePathPrivate 'C:\Temp\PrivatePGP1.asc' -Password 'ZielonaMila9!' -FilePath $PSScriptRoot\Encoded\Test1.txt.pgp -OutFilePath $PSScriptRoot\Decoded\Test4.txt 9 | 10 | #Protect-PGP -FilePathPublic "\\ad1\c$\Temp\PublicPGP1.asc" -FilePath "\\AD1.AD.EVOTEC.XYZ\c`$\Temp\PrivatePGP1.asc" -OutFilePath "\\AD1.AD.EVOTEC.XYZ\c`$\Temp\PrivatePGP1-Encrypted.asc.pgp" 11 | #Unprotect-PGP -FilePathPrivate '\\ad1\c$\Temp\PrivatePGP1.asc' -Password 'ZielonaMila9!' -FilePath "\\AD1.AD.EVOTEC.XYZ\c`$\Temp\PrivatePGP1-Encrypted.asc.pgp" -OutFilePath "\\AD1.AD.EVOTEC.XYZ\c`$\Temp\PrivatePGP1-Encrypted.asc.pgp.asc" -------------------------------------------------------------------------------- /Examples/Example-DecryptFolder.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | Unprotect-PGP -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP1.asc -Password 'ZielonaMila9!' -FolderPath $PSScriptRoot\Encoded -OutputFolderPath $PSScriptRoot\Decoded -------------------------------------------------------------------------------- /Examples/Example-DecryptString.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | $ProtectedString = Protect-PGP -FilePathPublic "$PSScriptRoot\Keys\PublicPGP1.asc" -String "This is string to encrypt" 4 | 5 | Unprotect-PGP -FilePathPrivate "$PSScriptRoot\Keys\PrivatePGP1.asc" -Password 'ZielonaMila9!' -String $ProtectedString -------------------------------------------------------------------------------- /Examples/Example-EncryptFolder.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP1.asc -FolderPath $PSScriptRoot\Test -OutputFolderPath $PSScriptRoot\Encoded -------------------------------------------------------------------------------- /Examples/Example-EncryptFolderMultipleKeys.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | Protect-PGP -FilePathPublic @("$PSScriptRoot\Keys\PublicPGP.asc", "$PSScriptRoot\Keys\PublicPGP2.asc") -FolderPath $PSScriptRoot\Test -OutputFolderPath $PSScriptRoot\Encoded 4 | 5 | Protect-PGP -FilePathPublic @("$PSScriptRoot\Keys\PublicPGP.asc", "$PSScriptRoot\Keys\PublicPGP2.asc") -FolderPath '~\Downloads\Cloudflare' -OutputFolderPath '~\Downloads\Cloudflare' -------------------------------------------------------------------------------- /Examples/Example-EncryptString.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -String "This is string to encrypt" -HashAlgorithm Sha256 -CompressionAlgorithm Zip -------------------------------------------------------------------------------- /Examples/Example-EncryptStringMultipleKeys.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | $String = Protect-PGP -FilePathPublic @("$PSScriptRoot\Keys\PublicPGP1.asc", "$PSScriptRoot\Keys\PublicPGP2.asc") -String "This is string to encrypt" 4 | $String 5 | 6 | 7 | Unprotect-PGP -String $String -FilePathPrivate "$PSScriptRoot\Keys\PrivatePGP1.asc" -Password 'ZielonaMila9!' 8 | 9 | Unprotect-PGP -String $String -FilePathPrivate "$PSScriptRoot\Keys\PrivatePGP2.asc" -Password 'ZielonaMila10!' -------------------------------------------------------------------------------- /Examples/Example-EncryptStringNoPassword.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | # Using public key to encrypt a file 4 | $EncryptedString = Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP3.asc -String "This is string to encrypt" 5 | 6 | # Using private key to decrypt a file without any password 7 | Unprotect-PGP -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP3.asc -String $EncryptedString -------------------------------------------------------------------------------- /Examples/Example-GeneratePGP.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | New-PGPKey -HashAlgorithm Sha512 -FilePathPublic $PSScriptRoot\Keys\PublicPGP1.asc -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP1.asc -UserName 'przemyslaw.klys' -Password 'ZielonaMila9!' 4 | New-PGPKey -FilePathPublic $PSScriptRoot\Keys\PublicPGP2.asc -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP2.asc -Password 'ZielonaMila10!' -UserName 'przemyslaw.klys@evotec.pl' 5 | 6 | # No password is required if you don't want one and just trust the key 7 | #New-PGPKey -FilePathPublic $PSScriptRoot\Keys\PublicPGP3.asc -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP3.asc -Strength 4096 -Certainty 8 8 | New-PGPKey -HashAlgorithm Sha512 -FilePathPublic $PSScriptRoot\Keys\PublicPGP3.asc -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP3.asc 9 | 10 | <# 11 | New-PGPKey -FilePathPublic '\\ad1\c$\Temp\PublicPGP1.asc' -FilePathPrivate '\\ad1\c$\Temp\PrivatePGP1.asc' -UserName 'przemyslaw.klys' -Password 'ZielonaMila9!' 12 | New-PGPKey -FilePathPublic '\\ad1\c$\Temp\PublicPGP2.asc' -FilePathPrivate '\\ad1\c$\Temp\PrivatePGP2.asc' -UserName 'przemyslaw.klys' -Password 'ZielonaMila10!' 13 | # No password is required if you don't want one and just trust the key 14 | New-PGPKey -FilePathPublic '\\ad1\c$\Temp\PublicPGP3.asc' -FilePathPrivate '\\ad1\c$\Temp\PrivatePGP3.asc' 15 | #> -------------------------------------------------------------------------------- /Examples/Example-VerifySignature.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\PSPGP.psd1 -Force 2 | 3 | $ProtectedString = Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -String "This is string to encrypt" 4 | 5 | Test-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -String $ProtectedString 6 | 7 | Test-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -FolderPath $PSScriptRoot\Encoded -------------------------------------------------------------------------------- /Examples/Test/Test1.txt: -------------------------------------------------------------------------------- 1 | This is my test1 -------------------------------------------------------------------------------- /Examples/Test/Test2.txt: -------------------------------------------------------------------------------- 1 | This is my test2 -------------------------------------------------------------------------------- /Examples/Test/Test3.txt: -------------------------------------------------------------------------------- 1 | This is my test3 -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Evotec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PSPGP.AzurePipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: Build_Windows 3 | pool: 4 | vmImage: windows-latest 5 | steps: 6 | - powershell: | 7 | Set-PSRepository PSGallery -InstallationPolicy Trusted 8 | Install-Module PSPublishModule -ErrorAction Stop -Force -Verbose 9 | .\Build\BuildModule.ps1 10 | displayName: "Run Pester Tests - PowerShell 5" 11 | - pwsh: | 12 | Set-PSRepository PSGallery -InstallationPolicy Trusted 13 | Install-Module PSPublishModule -ErrorAction Stop -Force -Verbose 14 | .\Build\BuildModule.ps1 15 | displayName: "Run Pester Tests - PowerShell 7+" 16 | 17 | - job: Build_PSCore_Ubuntu1604 18 | pool: 19 | vmImage: ubuntu-latest 20 | steps: 21 | - pwsh: | 22 | Set-PSRepository PSGallery -InstallationPolicy Trusted 23 | Install-Module PSPublishModule -ErrorAction Stop -Force -Verbose 24 | .\Build\BuildModule.ps1 25 | displayName: "Run Pester Tests" 26 | 27 | - job: Build_PSCore_MacOS1013 28 | pool: 29 | vmImage: macOS-latest 30 | steps: 31 | - pwsh: | 32 | Set-PSRepository PSGallery -InstallationPolicy Trusted 33 | Install-Module PSPublishModule -ErrorAction Stop -Force -Verbose 34 | .\Build\BuildModule.ps1 35 | displayName: "Run Pester Tests" 36 | -------------------------------------------------------------------------------- /PSPGP.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ModuleName = (Get-ChildItem $PSScriptRoot\*.psd1).BaseName 2 | $PrimaryModule = Get-ChildItem -Path $PSScriptRoot -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1 3 | if (-not $PrimaryModule) { 4 | throw "Path $PSScriptRoot doesn't contain PSD1 files. Failing tests." 5 | } 6 | if ($PrimaryModule.Count -ne 1) { 7 | throw 'More than one PSD1 files detected. Failing tests.' 8 | } 9 | $PSDInformation = Import-PowerShellDataFile -Path $PrimaryModule.FullName 10 | $RequiredModules = @( 11 | 'Pester' 12 | 'PSWriteColor' 13 | if ($PSDInformation.RequiredModules) { 14 | $PSDInformation.RequiredModules 15 | } 16 | ) 17 | foreach ($Module in $RequiredModules) { 18 | if ($Module -is [System.Collections.IDictionary]) { 19 | $Exists = Get-Module -ListAvailable -Name $Module.ModuleName 20 | if (-not $Exists) { 21 | Write-Warning "$ModuleName - Downloading $($Module.ModuleName) from PSGallery" 22 | Install-Module -Name $Module.ModuleName -Force -SkipPublisherCheck 23 | } 24 | } else { 25 | $Exists = Get-Module -ListAvailable $Module -ErrorAction SilentlyContinue 26 | if (-not $Exists) { 27 | Install-Module -Name $Module -Force -SkipPublisherCheck 28 | } 29 | } 30 | } 31 | 32 | Write-Color 'ModuleName: ', $ModuleName, ' Version: ', $PSDInformation.ModuleVersion -Color Yellow, Green, Yellow, Green -LinesBefore 2 33 | Write-Color 'PowerShell Version: ', $PSVersionTable.PSVersion -Color Yellow, Green 34 | Write-Color 'PowerShell Edition: ', $PSVersionTable.PSEdition -Color Yellow, Green 35 | Write-Color 'Required modules: ' -Color Yellow 36 | foreach ($Module in $PSDInformation.RequiredModules) { 37 | if ($Module -is [System.Collections.IDictionary]) { 38 | Write-Color ' [>] ', $Module.ModuleName, ' Version: ', $Module.ModuleVersion -Color Yellow, Green, Yellow, Green 39 | } else { 40 | Write-Color ' [>] ', $Module -Color Yellow, Green 41 | } 42 | } 43 | Write-Color 44 | 45 | Import-Module $PSScriptRoot\*.psd1 -Force 46 | $result = Invoke-Pester -Script $PSScriptRoot\Tests -Verbose -PassThru 47 | 48 | if ($result.FailedCount -gt 0) { 49 | throw "$($result.FailedCount) tests failed." 50 | } -------------------------------------------------------------------------------- /PSPGP.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | AliasesToExport = @() 3 | Author = 'Przemyslaw Klys' 4 | CmdletsToExport = @() 5 | CompanyName = 'Evotec' 6 | CompatiblePSEditions = @('Desktop', 'Core') 7 | Copyright = '(c) 2011 - 2024 Przemyslaw Klys @ Evotec. All rights reserved.' 8 | Description = 'PSPGP is a PowerShell module that provides PGP functionality in PowerShell. It allows encrypting and decrypting files/folders and strings using PGP.' 9 | DotNetFrameworkVersion = '4.7.2' 10 | FunctionsToExport = @('New-PGPKey', 'Protect-PGP', 'Test-PGP', 'Unprotect-PGP') 11 | GUID = 'edbf6d52-2d66-405e-a4d4-d4a95db8fb45' 12 | ModuleVersion = '0.1.13' 13 | PowerShellVersion = '5.1' 14 | PrivateData = @{ 15 | PSData = @{ 16 | ExternalModuleDependencies = @('Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility') 17 | IconUri = 'https://evotec.xyz/wp-content/uploads/2021/08/PSPGP.png' 18 | LicenseUri = 'https://github.com/EvotecIT/PSPGP/blob/master/License' 19 | ProjectUri = 'https://github.com/EvotecIT/PSPGP' 20 | Tags = @('pgp', 'gpg', 'encrypt', 'decrypt', 'windows', 'macos', 'linux') 21 | } 22 | } 23 | RequiredModules = @('Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility') 24 | RootModule = 'PSPGP.psm1' 25 | } -------------------------------------------------------------------------------- /PSPGP.psm1: -------------------------------------------------------------------------------- 1 | # Get public and private function definition files. 2 | $Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 3 | $Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 4 | $Classes = @( Get-ChildItem -Path $PSScriptRoot\Classes\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 5 | $Enums = @( Get-ChildItem -Path $PSScriptRoot\Enums\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 6 | # Get all assemblies 7 | $AssemblyFolders = Get-ChildItem -Path $PSScriptRoot\Lib -Directory -ErrorAction SilentlyContinue 8 | 9 | # Lets find which libraries we need to load 10 | $Default = $false 11 | $Core = $false 12 | $Standard = $false 13 | foreach ($A in $AssemblyFolders.Name) { 14 | if ($A -eq 'Default') { 15 | $Default = $true 16 | } elseif ($A -eq 'Core') { 17 | $Core = $true 18 | } elseif ($A -eq 'Standard') { 19 | $Standard = $true 20 | } 21 | } 22 | if ($Standard -and $Core -and $Default) { 23 | $FrameworkNet = 'Default' 24 | $Framework = 'Standard' 25 | } elseif ($Standard -and $Core) { 26 | $Framework = 'Standard' 27 | $FrameworkNet = 'Standard' 28 | } elseif ($Core -and $Default) { 29 | $Framework = 'Core' 30 | $FrameworkNet = 'Default' 31 | } elseif ($Standard -and $Default) { 32 | $Framework = 'Standard' 33 | $FrameworkNet = 'Default' 34 | } elseif ($Standard) { 35 | $Framework = 'Standard' 36 | $FrameworkNet = 'Standard' 37 | } elseif ($Core) { 38 | $Framework = 'Core' 39 | $FrameworkNet = '' 40 | } elseif ($Default) { 41 | $Framework = '' 42 | $FrameworkNet = 'Default' 43 | } else { 44 | Write-Error -Message 'No assemblies found' 45 | } 46 | if ($PSEdition -eq 'Core') { 47 | $LibFolder = $Framework 48 | } else { 49 | $LibFolder = $FrameworkNet 50 | } 51 | 52 | $Assembly = @( 53 | if ($Framework -and $PSEdition -eq 'Core') { 54 | Get-ChildItem -Path $PSScriptRoot\Lib\$Framework\*.dll -ErrorAction SilentlyContinue -Recurse 55 | } 56 | if ($FrameworkNet -and $PSEdition -ne 'Core') { 57 | Get-ChildItem -Path $PSScriptRoot\Lib\$FrameworkNet\*.dll -ErrorAction SilentlyContinue -Recurse 58 | } 59 | # if ($AssemblyFolders.BaseName -contains 'Standard') { 60 | # @( Get-ChildItem -Path $PSScriptRoot\Lib\Standard\*.dll -ErrorAction SilentlyContinue -Recurse) 61 | # } 62 | # if ($PSEdition -eq 'Core') { 63 | # @( Get-ChildItem -Path $PSScriptRoot\Lib\Core\*.dll -ErrorAction SilentlyContinue -Recurse ) 64 | # } else { 65 | # @( Get-ChildItem -Path $PSScriptRoot\Lib\Default\*.dll -ErrorAction SilentlyContinue -Recurse ) 66 | # } 67 | ) 68 | 69 | # This is special way of importing DLL if multiple frameworks are in use 70 | $FoundErrors = @( 71 | # We load the DLL that does OnImportRemove if we have special module that requires special treatment for binary modules 72 | 73 | # Get library name, from the PSM1 file name 74 | $LibraryName = $myInvocation.MyCommand.Name.Replace(".psm1", "") 75 | $Library = "$LibraryName.dll" 76 | $Class = "$LibraryName.Initialize" 77 | 78 | try { 79 | $ImportModule = Get-Command -Name Import-Module -Module Microsoft.PowerShell.Core 80 | 81 | if (-not ($Class -as [type])) { 82 | & $ImportModule ([IO.Path]::Combine($PSScriptRoot, 'Lib', $LibFolder, $Library)) -ErrorAction Stop 83 | } else { 84 | $Type = "$Class" -as [Type] 85 | & $importModule -Force -Assembly ($Type.Assembly) 86 | } 87 | } catch { 88 | Write-Warning -Message "Importing module $Library failed. Fix errors before continuing. Error: $($_.Exception.Message)" 89 | $true 90 | } 91 | 92 | Foreach ($Import in @($Assembly)) { 93 | try { 94 | Add-Type -Path $Import.Fullname -ErrorAction Stop 95 | } catch [System.Reflection.ReflectionTypeLoadException] { 96 | Write-Warning "Processing $($Import.Name) Exception: $($_.Exception.Message)" 97 | $LoaderExceptions = $($_.Exception.LoaderExceptions) | Sort-Object -Unique 98 | foreach ($E in $LoaderExceptions) { 99 | Write-Warning "Processing $($Import.Name) LoaderExceptions: $($E.Message)" 100 | } 101 | $true 102 | } catch { 103 | Write-Warning "Processing $($Import.Name) Exception: $($_.Exception.Message)" 104 | $LoaderExceptions = $($_.Exception.LoaderExceptions) | Sort-Object -Unique 105 | foreach ($E in $LoaderExceptions) { 106 | Write-Warning "Processing $($Import.Name) LoaderExceptions: $($E.Message)" 107 | } 108 | $true 109 | } 110 | } 111 | 112 | #Dot source the files 113 | Foreach ($Import in @($Private + $Classes + $Enums + $Public)) { 114 | Try { 115 | . $Import.Fullname 116 | } Catch { 117 | Write-Warning -Message "Failed to import functions from $($import.Fullname).Error: $($_.Exception.Message)" 118 | $true 119 | } 120 | } 121 | ) 122 | 123 | if ($FoundErrors.Count -gt 0) { 124 | $ModuleName = (Get-ChildItem $PSScriptRoot\*.psd1).BaseName 125 | Write-Warning "Importing module $ModuleName failed. Fix errors before continuing." 126 | break 127 | } 128 | 129 | Export-ModuleMember -Function '*' -Alias '*' -Cmdlet '*' -------------------------------------------------------------------------------- /Public/New-PGPKey.ps1: -------------------------------------------------------------------------------- 1 | function New-PGPKey { 2 | [cmdletBinding(DefaultParameterSetName = 'ClearText')] 3 | param( 4 | [parameter(Mandatory, ParameterSetName = 'Strength')] 5 | [parameter(Mandatory, ParameterSetName = 'StrengthCredential')] 6 | [parameter(Mandatory, ParameterSetName = 'ClearText')] 7 | [parameter(Mandatory, ParameterSetName = 'Credential')] 8 | [string] $FilePathPublic, 9 | [parameter(Mandatory, ParameterSetName = 'Strength')] 10 | [parameter(Mandatory, ParameterSetName = 'StrengthCredential')] 11 | [parameter(Mandatory, ParameterSetName = 'ClearText')] 12 | [parameter(Mandatory, ParameterSetName = 'Credential')] 13 | [string] $FilePathPrivate, 14 | [parameter(ParameterSetName = 'Strength')] 15 | [parameter(ParameterSetName = 'ClearText')] 16 | [string] $UserName, 17 | [parameter(ParameterSetName = 'Strength')] 18 | [parameter(ParameterSetName = 'ClearText')] 19 | [string] $Password, 20 | [parameter(Mandatory, ParameterSetName = 'StrengthCredential')] 21 | [parameter(Mandatory, ParameterSetName = 'Credential')] 22 | [pscredential] $Credential, 23 | [parameter(Mandatory, ParameterSetName = 'Strength')] 24 | [parameter(Mandatory, ParameterSetName = 'StrengthCredential')] 25 | [int] $Strength, 26 | [parameter(Mandatory, ParameterSetName = 'Strength')] 27 | [parameter(Mandatory, ParameterSetName = 'StrengthCredential')] 28 | [int] $Certainty, 29 | [parameter(ParameterSetName = 'Strength')] 30 | [parameter(ParameterSetName = 'StrengthCredential')] 31 | [switch] $EmitVersion, 32 | 33 | [alias('HashAlgorithmTag')][Org.BouncyCastle.Bcpg.HashAlgorithmTag] $HashAlgorithm, 34 | [Org.BouncyCastle.Bcpg.CompressionAlgorithmTag] $CompressionAlgorithm, 35 | [PgpCore.Enums.PGPFileType] $FileType, 36 | [Int32] $PgpSignatureType, 37 | [Org.BouncyCastle.Bcpg.PublicKeyAlgorithmTag] $PublicKeyAlgorithm, 38 | [Org.BouncyCastle.Bcpg.SymmetricKeyAlgorithmTag] $SymmetricKeyAlgorithm 39 | ) 40 | try { 41 | $PGP = [PgpCore.PGP]::new() 42 | } catch { 43 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 44 | throw 45 | } else { 46 | Write-Warning -Message "New-PGPKey - Creating keys genarated erorr: $($_.Exception.Message)" 47 | return 48 | } 49 | } 50 | if ($Credential) { 51 | $UserName = $Credential.UserName 52 | $Password = $Credential.GetNetworkCredential().Password 53 | } 54 | 55 | if ($PSBoundParameters.ContainsKey('HashAlgorithm')) { 56 | $PGP.HashAlgorithmTag = $HashAlgorithm 57 | } 58 | if ($PSBoundParameters.ContainsKey('CompressionAlgorithm')) { 59 | $PGP.CompressionAlgorithm = $CompressionAlgorithm 60 | } 61 | if ($PSBoundParameters.ContainsKey('FileType')) { 62 | $PGP.FileType = $FileType 63 | } 64 | if ($PSBoundParameters.ContainsKey('PgpSignatureType')) { 65 | $PGP.PgpSignatureType = $PgpSignatureType 66 | } 67 | if ($PSBoundParameters.ContainsKey('PublicKeyAlgorithm')) { 68 | $PGP.PublicKeyAlgorithm = $PublicKeyAlgorithm 69 | } 70 | if ($PSBoundParameters.ContainsKey('SymmetricKeyAlgorithm')) { 71 | $PGP.SymmetricKeyAlgorithm = $SymmetricKeyAlgorithm 72 | } 73 | 74 | $ResolvedPublicKey = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePathPublic) 75 | $ResolvedPrivateKey = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePathPrivate) 76 | 77 | try { 78 | if ($Strength) { 79 | $PGP.GenerateKey($ResolvedPublicKey, $ResolvedPrivateKey, $UserName, $Password, $Strength, $Certainty, $EmitVersion.IsPresent) 80 | } else { 81 | $PGP.GenerateKey($ResolvedPublicKey, $ResolvedPrivateKey, $UserName, $Password) 82 | } 83 | } catch { 84 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 85 | throw 86 | } else { 87 | Write-Warning -Message "New-PGPKey - Creating keys genarated error: $($_.Exception.Message)" 88 | return 89 | } 90 | } 91 | 92 | #void GenerateKey(string publicKeyFilePath, string privateKeyFilePath, string username, string password, int strength, int certainty, bool emitVersion) 93 | #void GenerateKey(System.IO.Stream publicKeyStream, System.IO.Stream privateKeyStream, string username, string password, int strength, int certainty, bool armor, bool emitVersion) 94 | } 95 | -------------------------------------------------------------------------------- /Public/Protect-PGP.ps1: -------------------------------------------------------------------------------- 1 | function Protect-PGP { 2 | [cmdletBinding(DefaultParameterSetName = 'File')] 3 | param( 4 | [Parameter(Mandatory, ParameterSetName = 'Folder')] 5 | [Parameter(Mandatory, ParameterSetName = 'File')] 6 | [Parameter(Mandatory, ParameterSetName = 'String')] 7 | [string[]] $FilePathPublic, 8 | 9 | [Parameter(Mandatory, ParameterSetName = 'Folder')][string] $FolderPath, 10 | [Parameter(ParameterSetName = 'Folder')][string] $OutputFolderPath, 11 | 12 | [Parameter(Mandatory, ParameterSetName = 'File')][string] $FilePath, 13 | [Parameter(ParameterSetName = 'File')][string] $OutFilePath, 14 | 15 | [Parameter(Mandatory, ParameterSetName = 'String')][string] $String, 16 | 17 | [System.IO.FileInfo] $SignKey, 18 | [string] $SignPassword, 19 | [alias('HashAlgorithmTag')][Org.BouncyCastle.Bcpg.HashAlgorithmTag] $HashAlgorithm, 20 | [Org.BouncyCastle.Bcpg.CompressionAlgorithmTag] $CompressionAlgorithm, 21 | [PgpCore.Enums.PGPFileType] $FileType, 22 | [Int32] $PgpSignatureType, 23 | [Org.BouncyCastle.Bcpg.PublicKeyAlgorithmTag] $PublicKeyAlgorithm, 24 | [Org.BouncyCastle.Bcpg.SymmetricKeyAlgorithmTag] $SymmetricKeyAlgorithm 25 | 26 | ) 27 | $PublicKeys = [System.Collections.Generic.List[System.IO.FileInfo]]::new() 28 | foreach ($FilePathPubc in $FilePathPublic) { 29 | $ResolvedPublicKey = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePathPubc) 30 | if (Test-Path -LiteralPath $ResolvedPublicKey) { 31 | $PublicKeys.Add([System.IO.FileInfo]::new($ResolvedPublicKey)) 32 | } else { 33 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 34 | throw 35 | } else { 36 | Write-Warning -Message "Protect-PGP - Public key doesn't exists $($ResolvedPublicKey): $($_.Exception.Message)" 37 | return 38 | } 39 | } 40 | } 41 | try { 42 | if ($SignKey) { 43 | $EncryptionKeys = [PgpCore.EncryptionKeys]::new($PublicKeys, $SignKey, $SignPassword) 44 | } else { 45 | $EncryptionKeys = [PgpCore.EncryptionKeys]::new($PublicKeys) 46 | } 47 | $PGP = [PgpCore.PGP]::new($EncryptionKeys) 48 | } catch { 49 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 50 | throw 51 | } else { 52 | Write-Warning -Message "Protect-PGP - Can't encrypt files because: $($_.Exception.Message)" 53 | return 54 | } 55 | } 56 | 57 | if ($PSBoundParameters.ContainsKey('HashAlgorithm')) { 58 | $PGP.HashAlgorithmTag = $HashAlgorithm 59 | } 60 | if ($PSBoundParameters.ContainsKey('CompressionAlgorithm')) { 61 | $PGP.CompressionAlgorithm = $CompressionAlgorithm 62 | } 63 | if ($PSBoundParameters.ContainsKey('FileType')) { 64 | $PGP.FileType = $FileType 65 | } 66 | if ($PSBoundParameters.ContainsKey('PgpSignatureType')) { 67 | $PGP.PgpSignatureType = $PgpSignatureType 68 | } 69 | if ($PSBoundParameters.ContainsKey('PublicKeyAlgorithm')) { 70 | $PGP.PublicKeyAlgorithm = $PublicKeyAlgorithm 71 | } 72 | if ($PSBoundParameters.ContainsKey('SymmetricKeyAlgorithm')) { 73 | $PGP.SymmetricKeyAlgorithm = $SymmetricKeyAlgorithm 74 | } 75 | 76 | if ($FolderPath) { 77 | $ResolvedFolderPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FolderPath) 78 | foreach ($File in Get-ChildItem -LiteralPath $ResolvedFolderPath -Recurse:$Recursive) { 79 | try { 80 | if ($OutputFolderPath) { 81 | $ResolvedOutputFolder = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFolderPath) 82 | $OutputFile = [io.Path]::Combine($ResolvedOutputFolder, "$($File.Name).pgp") 83 | if ($SignKey) { 84 | $PGP.EncryptFileAndSign($File.FullName, $Outputfile) 85 | } else { 86 | $PGP.EncryptFile($File.FullName, $OutputFile) 87 | } 88 | } else { 89 | if ($SignKey) { 90 | $PGP.EncryptFileAndSign($File.FullName, "$($File.FullName).pgp") 91 | } else { 92 | $PGP.EncryptFile($File.FullName, "$($File.FullName).pgp") 93 | } 94 | } 95 | } catch { 96 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 97 | throw 98 | } else { 99 | Write-Warning -Message "Protect-PGP - Can't encrypt file $($File.FullName): $($_.Exception.Message)" 100 | return 101 | } 102 | } 103 | } 104 | } elseif ($FilePath) { 105 | try { 106 | $ResolvedFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath) 107 | if ($OutFilePath) { 108 | $ResolvedOutFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutFilePath) 109 | if ($SignKey) { 110 | $PGP.EncryptFileAndSign($ResolvedFilePath, $ResolvedOutFilePath) 111 | } else { 112 | $PGP.EncryptFile($ResolvedFilePath, $ResolvedOutFilePath) 113 | } 114 | } else { 115 | if ($SignKey) { 116 | $PGP.EncryptFileAndSign($ResolvedFilePath, "$($ResolvedFilePath).pgp") 117 | } else { 118 | $PGP.EncryptFile($ResolvedFilePath, "$($ResolvedFilePath).pgp") 119 | } 120 | } 121 | } catch { 122 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 123 | throw 124 | } else { 125 | Write-Warning -Message "Protect-PGP - Can't encrypt file $($FilePath): $($_.Exception.Message)" 126 | return 127 | } 128 | } 129 | } elseif ($String) { 130 | try { 131 | if ($SignKey) { 132 | $PGP.EncryptArmoredStringAndSign($String) 133 | } else { 134 | $PGP.EncryptArmoredString($String) 135 | } 136 | } catch { 137 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 138 | throw 139 | } else { 140 | Write-Warning -Message "Protect-PGP - Can't encrypt string: $($_.Exception.Message)" 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Public/Test-PGP.ps1: -------------------------------------------------------------------------------- 1 | function Test-PGP { 2 | [cmdletBinding(DefaultParameterSetName = 'File')] 3 | param( 4 | [Parameter(Mandatory, ParameterSetName = 'Folder')] 5 | [Parameter(Mandatory, ParameterSetName = 'File')] 6 | [Parameter(Mandatory, ParameterSetName = 'String')] 7 | [string] $FilePathPublic, 8 | 9 | [Parameter(Mandatory, ParameterSetName = 'Folder')][string] $FolderPath, 10 | [Parameter(ParameterSetName = 'Folder')][string] $OutputFolderPath, 11 | 12 | [Parameter(Mandatory, ParameterSetName = 'File')][string] $FilePath, 13 | [Parameter(ParameterSetName = 'File')][string] $OutFilePath, 14 | 15 | [Parameter(Mandatory, ParameterSetName = 'String')][string] $String 16 | ) 17 | 18 | $ResolvedPublicKey = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePathPublic) 19 | if (Test-Path -LiteralPath $ResolvedPublicKey) { 20 | $PublicKey = [System.IO.FileInfo]::new($ResolvedPublicKey) 21 | } else { 22 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 23 | throw 24 | } else { 25 | Write-Warning -Message "Test-PGP - Public key doesn't exists $($ResolvedPublicKey): $($_.Exception.Message)" 26 | return 27 | } 28 | } 29 | try { 30 | $EncryptionKeys = [PgpCore.EncryptionKeys]::new($PublicKey) 31 | $PGP = [PgpCore.PGP]::new($EncryptionKeys) 32 | } catch { 33 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 34 | throw 35 | } else { 36 | Write-Warning -Message "Test-PGP - Can't test files because: $($_.Exception.Message)" 37 | return 38 | } 39 | } 40 | if ($FolderPath) { 41 | $ResolvedFolderPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FolderPath) 42 | foreach ($File in Get-ChildItem -LiteralPath $ResolvedFolderPath -Recurse:$Recursive) { 43 | try { 44 | $Output = $PGP.VerifyFile($File.FullName) 45 | $ErrorMessage = '' 46 | } catch { 47 | $Output = $false 48 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 49 | throw 50 | } else { 51 | Write-Warning -Message "Test-PGP - Can't test file $($File.FuleName): $($_.Exception.Message)" 52 | $ErrorMessage = $($_.Exception.Message) 53 | } 54 | } 55 | [PSCustomObject] @{ 56 | FilePath = $File.FullName 57 | Status = $Output 58 | Error = $ErrorMessage 59 | } 60 | } 61 | } elseif ($FilePath) { 62 | $ResolvedFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath) 63 | try { 64 | $Output = $PGP.VerifyFile($ResolvedFilePath) 65 | } catch { 66 | $Output = $false 67 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 68 | throw 69 | } else { 70 | Write-Warning -Message "Test-PGP - Can't test file $($ResolvedFilePath): $($_.Exception.Message)" 71 | $ErrorMessage = $($_.Exception.Message) 72 | } 73 | } 74 | [PSCustomObject] @{ 75 | FilePath = $ResolvedFilePath 76 | Status = $Output 77 | Error = $ErrorMessage 78 | } 79 | } elseif ($String) { 80 | try { 81 | $PGP.VerifyArmoredString($String) 82 | } catch { 83 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 84 | throw 85 | } else { 86 | Write-Warning -Message "Test-PGP - Can't test string: $($_.Exception.Message)" 87 | } 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /Public/Unprotect-PGP.ps1: -------------------------------------------------------------------------------- 1 | function Unprotect-PGP { 2 | [cmdletBinding(DefaultParameterSetName = 'FolderClearText')] 3 | param( 4 | [Parameter(Mandatory, ParameterSetName = 'FolderCredential')] 5 | [Parameter(Mandatory, ParameterSetName = 'FolderClearText')] 6 | [Parameter(Mandatory, ParameterSetName = 'FileCredential')] 7 | [Parameter(Mandatory, ParameterSetName = 'FileClearText')] 8 | [Parameter(Mandatory, ParameterSetName = 'StringClearText')] 9 | [Parameter(Mandatory, ParameterSetName = 'StringCredential')] 10 | [string] $FilePathPrivate, 11 | 12 | [Parameter(ParameterSetName = 'FolderClearText')] 13 | [Parameter(ParameterSetName = 'FileClearText')] 14 | [Parameter(ParameterSetName = 'StringClearText')] 15 | [string] $Password, 16 | 17 | [Parameter(Mandatory, ParameterSetName = 'FileCredential')] 18 | [Parameter(Mandatory, ParameterSetName = 'FolderCredential')] 19 | [Parameter(Mandatory, ParameterSetName = 'StringCredential')] 20 | [pscredential] $Credential, 21 | 22 | [Parameter(Mandatory, ParameterSetName = 'FolderCredential')] 23 | [Parameter(Mandatory, ParameterSetName = 'FolderClearText')] 24 | [string] $FolderPath, 25 | 26 | [Parameter(Mandatory, ParameterSetName = 'FolderCredential')] 27 | [Parameter(Mandatory, ParameterSetName = 'FolderClearText')] 28 | [string] $OutputFolderPath, 29 | 30 | [Parameter(Mandatory, ParameterSetName = 'FileCredential')] 31 | [Parameter(Mandatory, ParameterSetName = 'FileClearText')] 32 | [string] $FilePath, 33 | 34 | [Parameter(Mandatory, ParameterSetName = 'FileCredential')] 35 | [Parameter(Mandatory, ParameterSetName = 'FileClearText')] 36 | [string] $OutFilePath, 37 | 38 | [Parameter(Mandatory, ParameterSetName = 'StringClearText')] 39 | [Parameter(Mandatory, ParameterSetName = 'StringCredential')] 40 | [string] $String 41 | ) 42 | 43 | 44 | 45 | if ($Credential) { 46 | $Password = $Credential.GetNetworkCredential().Password 47 | } 48 | 49 | $ResolvedPrivateFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePathPrivate) 50 | if (-not (Test-Path -LiteralPath $ResolvedPrivateFile)) { 51 | Write-Warning -Message "Unprotect-PGP - Remove PGP encryption failed because private key file doesn't exists." 52 | return 53 | } 54 | $PrivateKey = Get-Content -LiteralPath $ResolvedPrivateFile -Raw 55 | try { 56 | $EncryptionKeys = [PgpCore.EncryptionKeys]::new($PrivateKey, $Password) 57 | 58 | $PGP = [PgpCore.PGP]::new($EncryptionKeys) 59 | } catch { 60 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 61 | throw 62 | } else { 63 | Write-Warning -Message "Unprotect-PGP - Can't decrypt files because: $($_.Exception.Message)" 64 | return 65 | } 66 | } 67 | if ($FolderPath) { 68 | $ResolvedFolderPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FolderPath) 69 | foreach ($File in Get-ChildItem -LiteralPath $ResolvedFolderPath -Recurse:$Recursive) { 70 | try { 71 | if ($OutputFolderPath) { 72 | $ResolvedOutputFolder = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFolderPath) 73 | $OutputFile = [io.Path]::Combine($ResolvedOutputFolder, "$($File.Name.Replace('.pgp',''))") 74 | $PGP.DecryptFile($File.FullName, $OutputFile) 75 | } else { 76 | $PGP.DecryptFile($File.FullName, "$($File.FullName)") 77 | } 78 | } catch { 79 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 80 | throw 81 | } else { 82 | Write-Warning -Message "Unprotect-PGP - Remove PGP encryption from $($File.FullName) failed: $($_.Exception.Message)" 83 | return 84 | } 85 | } 86 | } 87 | } elseif ($FilePath) { 88 | try { 89 | $ResolvedFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath) 90 | if ($OutFilePath) { 91 | $ResolvedOutFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutFilePath) 92 | $PGP.DecryptFile($ResolvedFilePath, $ResolvedOutFilePath) 93 | } else { 94 | $PGP.DecryptFile($ResolvedFilePath, "$($FilePath.Replace('.pgp',''))") 95 | } 96 | } catch { 97 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 98 | throw 99 | } else { 100 | Write-Warning -Message "Unprotect-PGP - Remove PGP encryption from $($FilePath) failed: $($_.Exception.Message)" 101 | return 102 | } 103 | } 104 | } elseif ($String) { 105 | try { 106 | $PGP.DecryptArmoredString($String) 107 | } catch { 108 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 109 | throw 110 | } else { 111 | Write-Warning -Message "Unprotect-PGP - Remove PGP encryption from string failed: $($_.Exception.Message)" 112 | return 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # PSPGP - PowerShell Module 2 | 3 |

4 | 5 | 6 | 7 | 8 |

9 | 10 |

11 | 12 | 13 | 14 | 15 |

16 | 17 |

18 | 19 | 20 | 21 |

22 | 23 | **PSPGP** is a PowerShell module that provides PGP functionality in PowerShell. It allows encrypting and decrypting files/folders and strings using PGP. 24 | **PSGPG** uses following .NET library to deliver this functionality: 25 | 26 | - [PgpCore](https://github.com/mattosaurus/PgpCore) - licensed MIT 27 | 28 | ## To install 29 | 30 | ```powershell 31 | Install-Module -Name PSPGP -AllowClobber -Force 32 | ``` 33 | 34 | Force and AllowClobber aren't necessary, but they do skip errors in case some appear. 35 | 36 | ## And to update 37 | 38 | ```powershell 39 | Update-Module -Name PSPGP 40 | ``` 41 | 42 | That's it. Whenever there's a new version, you run the command, and you can enjoy it. Remember that you may need to close, reopen PowerShell session if you have already used module before updating it. 43 | 44 | **The essential thing** is if something works for you on production, keep using it till you test the new version on a test computer. I do changes that may not be big, but big enough that auto-update may break your code. For example, a small rename to a parameter, and your code stops working! Be responsible! 45 | 46 | ## IMPORTANT 47 | 48 | This module works correctly on Windows/Linux and MacOS, but since it uses **.NET STANDARD 2.0** library it requires minimum of **.NET Framework 4.7.2** installed on a Windows machine when using PowerShell 5.1. Please make sure to keep your .NET Framework up to date on **Windows Client/Servers**. 49 | 50 | ## Using 51 | 52 | ### Create new PGP Public/Private Keys 53 | 54 | ```powershell 55 | New-PGPKey -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP.asc -UserName 'przemyslaw.klys' -Password 'ZielonaMila9!' 56 | ``` 57 | 58 | ### Encrypt Folder 59 | 60 | ```powershell 61 | Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -FolderPath $PSScriptRoot\Test -OutputFolderPath $PSScriptRoot\Encoded 62 | ``` 63 | 64 | ### Decrypt Folder 65 | 66 | ```powershell 67 | Unprotect-PGP -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP.asc -Password 'ZielonaMila9!' -FolderPath $PSScriptRoot\Encoded -OutputFolderPath $PSScriptRoot\Decoded 68 | ``` 69 | 70 | ### Encrypt / Decrypt String 71 | 72 | ```powershell 73 | $ProtectedString = Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -String "This is string to encrypt" 74 | Unprotect-PGP -FilePathPrivate $PSScriptRoot\Keys\PrivatePGP.asc -Password 'ZielonaMila9!' -String $ProtectedString 75 | ``` 76 | 77 | ### Verify signature 78 | 79 | ```powershell 80 | $ProtectedString = Protect-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -String "This is string to encrypt" 81 | 82 | Test-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -String $ProtectedString 83 | 84 | Test-PGP -FilePathPublic $PSScriptRoot\Keys\PublicPGP.asc -FolderPath $PSScriptRoot\Encoded 85 | ``` -------------------------------------------------------------------------------- /Sources/PSPGP.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32804.467 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSPGP", "PSPGP\PSPGP.csproj", "{EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Release|Any CPU.Build.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ExtensibilityGlobals) = postSolution 31 | SolutionGuid = {F37BB785-3AB1-4498-8102-689C0E4C3924} 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /Sources/PSPGP/Initialize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PSPGP { 8 | /// This is a dummy class that is used to initialize the module in PowerShell 9 | /// Doesnt do anything at all 10 | public class Initialize { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/PSPGP/OnImportAndRemove.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Management.Automation; 4 | using System.Reflection; 5 | 6 | public class OnModuleImportAndRemove : IModuleAssemblyInitializer, IModuleAssemblyCleanup { 7 | public void OnImport() { 8 | if (IsNetFramework()) { 9 | AppDomain.CurrentDomain.AssemblyResolve += MyResolveEventHandler; 10 | } 11 | } 12 | 13 | public void OnRemove(PSModuleInfo module) { 14 | if (IsNetFramework()) { 15 | AppDomain.CurrentDomain.AssemblyResolve -= MyResolveEventHandler; 16 | } 17 | } 18 | 19 | private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args) { 20 | //This code is used to resolve the assemblies 21 | //Console.WriteLine($"Resolving {args.Name}"); 22 | var directoryPath = Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location); 23 | var filesInDirectory = Directory.GetFiles(directoryPath); 24 | 25 | foreach (var file in filesInDirectory) { 26 | var fileName = Path.GetFileName(file); 27 | var assemblyName = Path.GetFileNameWithoutExtension(file); 28 | 29 | if (args.Name.StartsWith(assemblyName)) { 30 | //Console.WriteLine($"Loading {args.Name} assembly {fileName}"); 31 | return Assembly.LoadFile(file); 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | private bool IsNetFramework() { 38 | // Get the version of the CLR 39 | Version clrVersion = System.Environment.Version; 40 | // Check if the CLR version is 4.x.x.x 41 | return clrVersion.Major == 4; 42 | } 43 | 44 | private bool IsNetCore() { 45 | return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase); 46 | } 47 | 48 | private bool IsNet5OrHigher() { 49 | return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith(".NET 5", StringComparison.OrdinalIgnoreCase) || 50 | System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith(".NET 6", StringComparison.OrdinalIgnoreCase) || 51 | System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith(".NET 7", StringComparison.OrdinalIgnoreCase) || 52 | System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.StartsWith(".NET 8", StringComparison.OrdinalIgnoreCase); 53 | } 54 | } -------------------------------------------------------------------------------- /Sources/PSPGP/PSPGP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Evotec 5 | Przemyslaw Klys 6 | 1.0.0 7 | netstandard2.0 8 | PSPGP 9 | 10 | (c) 2011 - 2022 Przemyslaw Klys @ Evotec. All rights reserved. 11 | 10.0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | $(DefineConstants);FRAMEWORK 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tests/PSPGP.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'PGP Tests' { 2 | # prepare things 3 | $KeysDirectory = [io.path]::Combine($env:TEMP, 'Keys') 4 | $KeyPublic = [io.path]::Combine($KeysDirectory, 'PublicPGP.asc') 5 | $KeyPrivate = [io.path]::Combine($KeysDirectory, 'PrivatePGP.asc') 6 | 7 | $KeyPublic1 = [io.path]::Combine($KeysDirectory, 'PublicPGP1.asc') 8 | $KeyPrivate1 = [io.path]::Combine($KeysDirectory, 'PrivatePGP1.asc') 9 | [string] $Script:ProtectedString = '' 10 | [string] $Script:ProtectedString = '' 11 | 12 | BeforeAll { 13 | $KeysDirectory = [io.path]::Combine($env:TEMP, 'Keys') 14 | New-Item -Path $KeysDirectory -Force -ItemType Directory 15 | } 16 | It ' Running New-PGPKey with Username and password should create public and private keys' -TestCases @{ KeysDirectory = $KeysDirectory; KeyPublic = $KeyPublic; KeyPrivate = $KeyPrivate; KeyPublic1 = $KeyPublic1; KeyPrivate1 = $KeyPrivate1 } { 17 | New-PGPKey -FilePathPublic $KeyPublic -FilePathPrivate $KeyPrivate -UserName 'przemyslaw.klys' -Password 'ZielonaMila9!' 18 | Test-Path -LiteralPath $KeyPublic | Should -Be $true 19 | Test-Path -LiteralPath $KeyPrivate | Should -Be $true 20 | 21 | New-PGPKey -FilePathPublic $KeyPublic1 -FilePathPrivate $KeyPrivate1 -UserName 'przemyslaw.klys1' -Password 'ZielonaMila9!1' 22 | Test-Path -LiteralPath $KeyPublic1 | Should -Be $true 23 | Test-Path -LiteralPath $KeyPrivate1 | Should -Be $true 24 | } 25 | It ' Test script encryption' -TestCases @{ ProtectedString = $ProtectedString; KeysDirectory = $KeysDirectory; KeyPublic = $KeyPublic; KeyPrivate = $KeyPrivate } { 26 | $Script:ProtectedString = Protect-PGP -FilePathPublic $KeyPublic -String "This is string to encrypt" -ErrorAction Stop 27 | } 28 | 29 | It ' Test script encryption (multiple keys)' -TestCases @{ ProtectedString = $ProtectedString; KeysDirectory = $KeysDirectory; KeyPublic = $KeyPublic; KeyPublic1 = $KeyPublic1 } { 30 | $Script:ProtectedStringMultiple = Protect-PGP -FilePathPublic $KeyPublic, $KeyPublic1 -String "This is string to encrypt with multiple keys" -ErrorAction Stop 31 | } 32 | 33 | It ' Decrypt string' -TestCases @{ ProtectedString = $ProtectedString; KeysDirectory = $KeysDirectory; KeyPublic = $KeyPublic; KeyPrivate = $KeyPrivate } { 34 | $String = Unprotect-PGP -FilePathPrivate $KeyPrivate -Password 'ZielonaMila9!' -String $Script:ProtectedString 35 | $String | Should -Be "This is string to encrypt" 36 | } 37 | 38 | It ' Decrypt string (multiple keys)' -TestCases @{ ProtectedString = $ProtectedString; KeysDirectory = $KeysDirectory; KeyPrivate = $KeyPrivate; KeyPrivate1 = $KeyPrivate1 } { 39 | $String1 = Unprotect-PGP -FilePathPrivate $KeyPrivate1 -Password 'ZielonaMila9!1' -String $Script:ProtectedStringMultiple 40 | $String1 | Should -Be "This is string to encrypt with multiple keys" 41 | 42 | $String = Unprotect-PGP -FilePathPrivate $KeyPrivate -Password 'ZielonaMila9!' -String $Script:ProtectedStringMultiple 43 | $String | Should -Be "This is string to encrypt with multiple keys" 44 | 45 | 46 | } 47 | # clean everything 48 | AfterAll { 49 | $KeysDirectory = [io.path]::Combine($env:TEMP, 'Keys') 50 | Remove-Item -Path $KeysDirectory -Recurse -Force 51 | } 52 | } --------------------------------------------------------------------------------