diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5520579 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +src/Vendor/libressl255/* filter=lfs diff=lfs merge=lfs -text +*.pfx filter=lfs diff=lfs merge=lfs -text diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cf9fb42 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ + +language: csharp +dotnet: 2.2.101 +mono: none + +git: + depth: 1000 + +os: + - linux +# Disable OSX bulds for now +# - osx + +sudo: required + +dist: xenial +osx_image: xcode8.1 + +matrix: + fast_finish: true + + +addons: + artifacts: + #paths: $(ls ./../dist/PowerShellGet.zip | tr "\n" ":") + paths: ./dist/PowerShellGet.zip + + +install: + # Default 2.0.0 Ruby is buggy + # Default bundler version is buggy + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + rvm install ruby-2.3.3; + rvm --default use 2.3.3; + fi + - bash <(wget -O - https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.sh) + - pushd tools + - chmod +x travis.sh + - popd + +script: + - echo "TRAVIS_EVENT_TYPE value $TRAVIS_EVENT_TYPE" + - ./tools/travis.sh diff --git a/.vscode/launch.json b/.vscode/launch.json index a89c67e..affb74a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,48 +1,48 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File", - "script": "${file}", - "args": [], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File in Temporary Console", - "script": "${file}", - "args": [], - "cwd": "${file}", - "createTemporaryIntegratedConsole": true - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File w/Args Prompt", - "script": "${file}", - "args": [ - "${command:SpecifyScriptArgs}" - ], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "attach", - "name": "PowerShell Attach to Host Process", - "processId": "${command:PickPSHostProcess}", - "runspaceId": 1 - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Interactive Session", - "cwd": "${workspaceRoot}" - } - ] -} \ No newline at end of file +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File", + "script": "${file}", + "args": [], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File in Temporary Console", + "script": "${file}", + "args": [], + "cwd": "${file}", + "createTemporaryIntegratedConsole": true + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Launch Current File w/Args Prompt", + "script": "${file}", + "args": [ + "${command:SpecifyScriptArgs}" + ], + "cwd": "${file}" + }, + { + "type": "PowerShell", + "request": "attach", + "name": "PowerShell Attach to Host Process", + "processId": "${command:PickPSHostProcess}", + "runspaceId": 1 + }, + { + "type": "PowerShell", + "request": "launch", + "name": "PowerShell Interactive Session", + "cwd": "" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 05e9a14..39b2319 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,7 +26,8 @@ "powershell.codeFormatting.whitespaceAroundOperator": true, "powershell.codeFormatting.whitespaceAfterSeparator": true, "powershell.codeFormatting.ignoreOneLineBlock": true, - "powershell.codeFormatting.alignPropertyValuePairs": false, + "powershell.codeFormatting.alignPropertyValuePairs": true, + "powershell.codeFormatting.preset": "Custom", // cspell spellchecker options "cSpell.enabledLanguageIds": [ "c", diff --git a/README.md b/README.md index 650d148..dfb10b7 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,28 @@ need to store credentials for non interactive usage like in scheduled tasks. For more details read the [about_PSCredentialStore](/docs/about_PSCredentialStore.md) page on github or via CLI with `Get-Help about_PSCredentialStore`. +:exclamation: Upcoming Changes :exclamation: +================ + +The will be some breaking changes starting with the `0.5.0.xxx`: + +- **PSCredentialStore will use PFX certificates to encrypt your credentials.** + - This replaces the the current encryption methods and you need to recreate or upgrade your pre existing stores. +- The changes allows the PSCredentialStore module to support the PowerShell `Core` editions. + - Yes this means, you can use the module on any PowerShell 6 supported linux distribution. +- It's also possible to create a shared credential store and transfer it onto a another platform like: +`Windows -- to --> Linux` and vice versa. +- Automatically creates self signed certificate with 2048 bits RSA keys for encryption. + Installation ============ PowerShellGallery.com (Recommended Way) --------------------------------------- -* Make sure you use PowerShell 4.0 or higher with `$PSVersionTable`. -* Use the builtin PackageManagement and install with: `Install-Module PSCredentialStore` +* Make sure you use PowerShell 5.1 or higher with `$PSVersionTable`. +* Use the builtin PackageManagement and install with: `Import-Module PowerShellGet; Install-Module 'PSCredentialStore' -Repository 'PSGallery'` + * Additionally use the `-AllowPrerelease` switch until we publish the final release! * Done. Start exploring the Module with `Import-Module PSCredentialStore ; Get-Command -Module PSCredentialStore` Manual Way @@ -97,3 +111,13 @@ Connect-To -RemoteHost "fas.myside.local" -Type NetAppFAS Connect-To -RemoteHost "esx01.myside.local" -Type VMware Connect-To -RemoteHost "vcr.myside.local" -Type CisServer ``` + +Credits +------- + +A huge thanks to all the people who helped with their projects and indirect contributions which made this possible! + +- This module is inspired by the awesome work of @dlwyatt with articles like these: + - https://powershell.org/2013/11/24/saving-passwords-and-preventing-other-processes-from-decrypting-them/ + - https://powershell.org/2014/02/01/revisited-powershell-and-encryption/ +- The awesome people from [LibreSSL](http://www.libressl.org/) which publishes the [portable openssl/libressl binaries](https://github.com/libressl-portable/portable)! diff --git a/appveyor.yml b/appveyor.yml index 1f5730f..6512399 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.2.3.{build} +version: 0.5.0.{build} #branches: # only: @@ -20,6 +20,7 @@ image: Visual Studio 2017 install: - ps: Import-Module .\tools\AppVeyor.psm1 + - ps: Import-Module .\tools\CoverallsIO.psm1 - ps: Invoke-InstallDependencies environment: @@ -37,10 +38,10 @@ build_script: - ps: Invoke-AppVeyorBuild test_script: - - ps: Invoke-AppVeyorTests - ps: | + $CodeCoverage = Invoke-AppVeyorTests if ($null -ne $Env:CoverallsToken) { - Invoke-CoverageReport + Invoke-CoverageReport -PesterCoverageReport $CodeCoverage } else { Write-Warning "No CoverallsToken found. This build seems to be triggered by a PR. Skipping this step..." @@ -60,7 +61,7 @@ deploy: secure: M+bBX5/nKdJB0eViP7xtrLVTwf3vGDUA9N2MMprZp2i+9ZR3CBVcJnSzJWUmalhB artifact: PSCredentialStore.zip # upload all NuGet packages to release assets draft: false - prerelease: false + prerelease: true on: branch: master # build release on master branch changes diff --git a/assets/logo256.png b/assets/logo256.png new file mode 100644 index 0000000..92f71b9 Binary files /dev/null and b/assets/logo256.png differ diff --git a/docs/Get-CredentialStore.md b/docs/Get-CredentialStore.md index 4a9b63c..718c9af 100644 --- a/docs/Get-CredentialStore.md +++ b/docs/Get-CredentialStore.md @@ -63,7 +63,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/docs/Get-CredentialStoreItem.md b/docs/Get-CredentialStoreItem.md index c1c2fd5..eba717e 100644 --- a/docs/Get-CredentialStoreItem.md +++ b/docs/Get-CredentialStoreItem.md @@ -19,7 +19,7 @@ Get-CredentialStoreItem -RemoteHost [-Identifier ] [] -RemoteHost [-Identifier ] [-Shared] +Get-CredentialStoreItem -RemoteHost [-Identifier ] [-Shared] [-Path ] [] ``` @@ -93,7 +93,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/docs/New-CredentialStore.md b/docs/New-CredentialStore.md index c1c2fd5..eba717e 100644 --- a/docs/New-CredentialStore.md +++ b/docs/New-CredentialStore.md @@ -19,7 +19,7 @@ Get-CredentialStoreItem -RemoteHost [-Identifier ] [] -RemoteHost [-Identifier ] [-Shared] +Get-CredentialStoreItem -RemoteHost [-Identifier ] [-Shared] [-Path ] [] ``` @@ -93,7 +93,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/docs/New-CredentialStoreItem.md b/docs/New-CredentialStoreItem.md index 5896e9b..6b00dd3 100644 --- a/docs/New-CredentialStoreItem.md +++ b/docs/New-CredentialStoreItem.md @@ -20,8 +20,8 @@ New-CredentialStoreItem -RemoteHost [-Identifier ] [-Credential ### Shared ``` -New-CredentialStoreItem [-Path ] -RemoteHost [-Identifier ] - [-Credential ] [-Shared] [] +New-CredentialStoreItem -RemoteHost [-Identifier ] [-Credential ] [-Shared] + [-Path ] [] ``` ## DESCRIPTION @@ -51,7 +51,7 @@ Aliases: Required: False Position: Named Default value: None -Accept pipeline input: False +Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` @@ -111,7 +111,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/docs/Remove-CredentialStoreItem.md b/docs/Remove-CredentialStoreItem.md index 95e9dcd..2fa9bfa 100644 --- a/docs/Remove-CredentialStoreItem.md +++ b/docs/Remove-CredentialStoreItem.md @@ -19,7 +19,7 @@ Remove-CredentialStoreItem -RemoteHost [-Identifier ] [] -RemoteHost [-Identifier ] [-Shared] +Remove-CredentialStoreItem -RemoteHost [-Identifier ] [-Shared] [-Path ] [] ``` @@ -94,7 +94,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/docs/Set-CredentialStoreItem.md b/docs/Set-CredentialStoreItem.md index edc6771..da6d71e 100644 --- a/docs/Set-CredentialStoreItem.md +++ b/docs/Set-CredentialStoreItem.md @@ -14,13 +14,14 @@ Changes the credentials for the given remote host in the store. ### Private (Default) ``` -Set-CredentialStoreItem -RemoteHost [-Identifier ] [] +Set-CredentialStoreItem -RemoteHost [-Identifier ] [-Credential ] + [] ``` ### Shared ``` -Set-CredentialStoreItem [-Path ] -RemoteHost [-Identifier ] [-Shared] - [] +Set-CredentialStoreItem -RemoteHost [-Identifier ] [-Credential ] [-Shared] + [-Path ] [] ``` ## DESCRIPTION @@ -37,6 +38,21 @@ Set-CredentialStoreItem -Path "C:\TMP\mystore.json" -RemoteHost "esx01.myside.lo ## PARAMETERS +### -Credential +{{Fill Credential Description}} + +```yaml +Type: PSCredential +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + ### -Identifier Defaults to "". Specify a string, which separates two CredentialStoreItems for the @@ -94,7 +110,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/docs/Test-CredentialStore.md b/docs/Test-CredentialStore.md index 465d450..f3d1170 100644 --- a/docs/Test-CredentialStore.md +++ b/docs/Test-CredentialStore.md @@ -63,7 +63,7 @@ Type: SwitchParameter Parameter Sets: Shared Aliases: -Required: False +Required: True Position: Named Default value: False Accept pipeline input: False diff --git a/resources/cs/Broken_CS.json b/resources/cs/Broken_CS.json index 23e8eee..a3d7719 100644 --- a/resources/cs/Broken_CS.json +++ b/resources/cs/Broken_CS.json @@ -1,3 +1,3 @@ { - "Version": "1.2.0", - "Creation": "2016-06-14 08:41:10" + "Version": "2.0.0", + "Creation": "2016-06-14 08:41:10" diff --git a/resources/cs/Challenge.bin b/resources/cs/Challenge.bin deleted file mode 100644 index 3b80551..0000000 --- a/resources/cs/Challenge.bin +++ /dev/null @@ -1 +0,0 @@ -!pH4"=wS2 \ No newline at end of file diff --git a/resources/cs/CredentialStore.json b/resources/cs/CredentialStore.json index 4d45b38..14c94fe 100644 Binary files a/resources/cs/CredentialStore.json and b/resources/cs/CredentialStore.json differ diff --git a/resources/cs/PSCredentialStore.pfx b/resources/cs/PSCredentialStore.pfx new file mode 100644 index 0000000..3ee39bd --- /dev/null +++ b/resources/cs/PSCredentialStore.pfx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec0e0f92ec39ec25158093c254e8edd7013c9a079fb74960a9d73585f9212ead +size 2749 diff --git a/src/Certificate/New-CRTAttribute.ps1 b/src/Certificate/New-CRTAttribute.ps1 new file mode 100644 index 0000000..30d76dc --- /dev/null +++ b/src/Certificate/New-CRTAttribute.ps1 @@ -0,0 +1,100 @@ +function New-CRTAttribute { + <# + .SYNOPSIS + Create required data for a certificate signing request. + + .DESCRIPTION + Defines the certificate related properties for an upcoming New-PfxCertificate execution. + + .PARAMETER Country + Provide a two letter country code. + + .PARAMETER State + Certificate state value. + + .PARAMETER City + Certificate city value. + + .PARAMETER Organization + Certificate organization value. + + .PARAMETER OrganizationalUnitName + Certificate OrganizationalUnitName value. + + .PARAMETER CommonName + The certificate common name. + + .PARAMETER CSRSubject + you can provide the needed certificate properties with in one hashtable. This hashtable has to contain the + following keys: 'Country', 'State', 'City', 'Organization', 'OrganizationalUnitName', 'CommonName'. + + .INPUTS + [None] + + .OUTPUTS + ['PSCredentialStore.Certificate.CSRDetails'] + + .EXAMPLE + New-CRTAttribute -CSRSubject @{Country = 'DE'; State = 'BW'; City = 'Karlsruhe'; Organization = 'AwesomeIT'; OrganizationalUnitName = '';CommonName = 'MyPrivateCert'} + + .NOTES + File Name : New-CSRDetails.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + [CmdletBinding()] + [OutputType('PSCredentialStore.Certificate.Attribute')] + param( + [Parameter(Mandatory = $true)] + [ValidateLength(2, 2)] + [ValidateNotNull()] + [string]$Country, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [string]$State, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [string]$City, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [string]$Organization, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [string]$OrganizationalUnitName, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [string]$CommonName, + + [Parameter(Mandatory = $false)] + [ValidateNotNull()] + [int]$Days = 365 + ) + begin { + + } + process { + return [PSCustomObject]@{ + PSTypeName = 'PSCredentialStore.Certificate.Attribute' + Subject = [PSCustomObject]@{ + PSTypeName = 'PSCredentialStore.Certificate.Attribute.Subject' + Country = $Country + State = $State + City = $City + Organization = $Organization + OrganizationalUnitName = $OrganizationalUnitName + CommonName = $CommonName + } + Days = $Days + } + } + end { + } +} diff --git a/src/Certificate/New-PfxCertificate.ps1 b/src/Certificate/New-PfxCertificate.ps1 new file mode 100644 index 0000000..4fb7a40 --- /dev/null +++ b/src/Certificate/New-PfxCertificate.ps1 @@ -0,0 +1,142 @@ +function New-PfxCertificate { + <# + .SYNOPSIS + Creates new PFX certificate for the CredentialStore encryption. + + .DESCRIPTION + Use this function to create a custom self signed certificate used by the PSCredentialStore module. + + .PARAMETER CRTAttribute + Provide certificate related attributes provided by function New-CRTAttribute. + + .PARAMETER KeyName + Provide a custom full path and name for the private key. The file extension has to be `*.key`. + + .PARAMETER CertName + Provide a custom full path and name for the PFX certificate file. The file extension has to be `*.pfx` + + .INPUTS + [PSCredentialStore.Certificate.Attribute] + + .OUTPUTS + [None] + + .EXAMPLE + New-PfxCertificate -CRTAttribute $CRTAttribute -KeyName './myprivate.key' -CertName './mycert.pfx' + + .NOTES + File Name : New-PfxCertificate.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + [CmdletBinding(SupportsShouldProcess = $true)] + [OutputType()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [ValidateNotNullOrEmpty()] + [PSTypeName('PSCredentialStore.Certificate.Attribute')]$CRTAttribute, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$KeyName = './private.key', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$CertName = './certificate.pfx' + ) + + begin { + $ModuleBase = Get-ModuleBase + if ($isLinux -or $isMacOS) { + try { + $openssl = Get-Command -Name 'openssl' -ErrorAction Stop + } + catch { + $_.Exception.Message | Write-Error + $ErrorParams = @{ + Message = 'Can not find the openssl binary!' + ErrorAction = 'Stop' + Exception = [System.IO.FileNotFoundException]::new() + } + Write-Error @ErrorParams + } + } + elseif (($PSVersionTable.PSEdition -eq 'Desktop' -and $PSVersionTable.PSVersion.Major -lt 6) -or ($IsWindows -eq $true)) { + $openssl = Join-Path -Path $ModuleBase -ChildPath '/Vendor/libressl255/openssl.exe' + } + + $Env:OPENSSL_CONF = Join-Path $ModuleBase -ChildPath '/openssl.conf' + } + process { + $SubjPattern = "/C={0}/ST={1}/L={2}/O={3}/OU={4}/CN={5}" + $SubjValues = @( + $CRTAttribute.Subject.Country, + $CRTAttribute.Subject.State, + $CRTAttribute.Subject.City, + $CRTAttribute.Subject.Organization, + $CRTAttribute.Subject.OrganizationalUnitName, + $CRTAttribute.Subject.CommonName + ) + $Subj = $SubjPattern -f $SubjValues + + $PEMCertName = $CertName -replace '.pfx', '.crt' + $ExpPattern = '& ''{0}'' req -x509 -sha256 -nodes -days {1} -newkey rsa:2048 -keyout {2} -out {3} -subj "{4}" *>$null' + $ExpValues = @( + $openssl, + $CRTAttribute.Days + $KeyName, + $PEMCertName, + $Subj + ) + $PEMExp = $ExpPattern -f $ExpValues + + Write-Verbose -Message ( 'Expr string is: {0}' -f $PEMExp) + + # Edit the Error action for the openSLL command to make the redirect *>$null work. + # There is always a stderr and stdout stream! + $EAP = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + Invoke-Expression -Command $PEMExp + $ErrorActionPreference = $EAP + + # manually testing the openssl command results + + if (! (Test-Path -Path $KeyName)) { + $ErrorParams = @{ + Message = 'Could not create the private key ${0}' -f $KeyName + ErrorAction = 'Stop' + Exception = [System.UnauthorizedAccessException]::new() + } + Write-Error @ErrorParams + } + if (! (Test-Path -Path $PEMCertName)) { + $ErrorParams = @{ + Message = 'Could not create the PEM certificate ${0}' -f $PEMCertName + ErrorAction = 'Stop' + Exception = [System.Exception]::new() + } + Write-Error @ErrorParams + } + + $PfxPattern = '& ''{0}'' pkcs12 -export -out {1} -inkey {2} -in {3} -passout pass:' + $PfxValues = @( + $openssl, + $CertName, + $KeyName, + ($CertName -replace '.pfx', '.crt') + ) + $PfxExp = $PfxPattern -f $PfxValues + Write-Verbose -Message ( 'PfxExp string is: {0}' -f $PfxExp) + Invoke-Expression -Command $PfxExp + + # Remove private key and crt file. Always ask user + Remove-Item -Path $KeyName + Remove-Item -Path ($CertName -replace '.pfx', '.crt') + } + end { + Remove-Item Env:\OPENSSL_CONF -Confirm:$False -Force -ErrorAction SilentlyContinue + } +} diff --git a/src/Certificate/Use-PfxCertificate.ps1 b/src/Certificate/Use-PfxCertificate.ps1 new file mode 100644 index 0000000..e1802d7 --- /dev/null +++ b/src/Certificate/Use-PfxCertificate.ps1 @@ -0,0 +1,102 @@ +function Use-PfxCertificate { + <# + .SYNOPSIS + Links an existing PFX Certifiacte to a CredentialStore. + + .DESCRIPTION + Linking a certificate is needed if you plan to use the same CredentialStore in cross platform scenarios. + + .PARAMETER Path + Specify the path to the PFX Certificate you want to link for usage. + + .INPUTS + [None] + + .OUTPUTS + [None] + + .EXAMPLE + + + .NOTES + File Name : Use-PfxCertificate.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + [CmdletBinding(DefaultParameterSetName = "Private")] + [OutputType()] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] + param( + [Parameter(Mandatory = $true, ParameterSetName = "Private")] + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] + [ValidateNotNullOrEmpty()] + [string]$Path, + + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] + [ValidateNotNullOrEmpty()] + [string]$CredentialStore, + + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] + [switch]$Shared + ) + begin {} + + process { + try { + # We need to resolve the path to make sure it has the correct platform specific syntax. + # And it should also exist. + $validPath = Resolve-Path -Path $Path -ErrorAction Stop + $PfxCertificate = Get-PfxCertificate -FilePath $validPath -ErrorAction Stop + } + catch { + $_.Exception.Error | Write-Error + $ErrorParams = @{ + Message = 'The given PFX certificate does not exist!' + ErrorAction = 'Stop' + } + Write-Error @ErrorParams + } + + try { + if ($PSCmdlet.ParameterSetName -eq "Private") { + $StorePath = Get-DefaultCredentialStorePath + $CS = Get-CredentialStore + } + elseif ($PSCmdlet.ParameterSetName -eq "Shared" ) { + if (!($PSBoundParameters.ContainsKey('CredentialStore'))) { + $StorePath = Get-DefaultCredentialStorePath -Shared + $CS = Get-CredentialStore -Shared + } + else { + $StorePath = $CredentialStore + $CS = Get-CredentialStore -Shared -Path $CredentialStore + } + } + } + catch { + $_.Exception.Error | Write-Error + $ErrorParams = @{ + Message = 'The given CredentialStore does not exist!' + ErrorAction = 'Stop' + } + Write-Error @ErrorParams + } + + # Lets first check if the thumbprint matches + if (($CS.Thumbprint -notmatch $PfxCertificate.Thumbprint) -and ($CS.Thumbprint.Length -ne 0)) { + Write-Warning @" +You are trying to map an unknown certificate. +Make sure you used the same AES keys for encrypting! +"@ + } + + $CS.PfxCertificate = $validPath.Path + $CS.Thumbprint = $PfxCertificate.Thumbprint + $CS | ConvertTo-Json -Depth 5 | Out-File -FilePath $StorePath -Force -Encoding utf8 + } + + end {} +} diff --git a/src/ChallengeFile/Get-ChallengeFile.ps1 b/src/ChallengeFile/Get-ChallengeFile.ps1 deleted file mode 100644 index b5af369..0000000 --- a/src/ChallengeFile/Get-ChallengeFile.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -function Get-ChallengeFile { - <# - .SYNOPSIS - Reads the challenge file as binary content. - - .DESCRIPTION - Use this function to tread a challenge file. Returns a [Byte[]] Array. - - .PARAMETER Path - Specify a file to read. - - .INPUTS - [None] - - .OUTPUTS - [Byte[]] - - .EXAMPLE - .\Get-RandomKey -Path "C:\TMP\Challenge.bin" - - .NOTES - ``` - File Name : Get-ChallengeFile.ps1 - Author : Marco Blessing - marco.blessing@googlemail.com - Requires : - ``` - .LINK - https://github.com/OCram85/PSCredentialStore - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory = $false)] - [string]$Path = "{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData - ) - - if (Test-Path $Path) { - try { - [io.file]::ReadAllBytes($Path) - } - catch { - Write-Error ("Could not read file {0}." -f $Path) -ErrorAction Stop - } - } -} diff --git a/src/ChallengeFile/Set-ChallengeFile.ps1 b/src/ChallengeFile/Set-ChallengeFile.ps1 deleted file mode 100644 index 926c3f5..0000000 --- a/src/ChallengeFile/Set-ChallengeFile.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -Function Set-ChallengeFile() { - <# - .SYNOPSIS - Writes the given key into the challenge file - - .DESCRIPTION - You can use the file content for ConvertTo-SecureString operations. - - .PARAMETER Path - The file you wish to create. - - .PARAMETER KeySize - Specify the key size for the encryption key. - - .PARAMETER Force - Use this switch to override an older file version. - - .INPUTS - [None] - - .OUTPUTS - [None] - - .EXAMPLE - .\Set-ChallengeFile -Path "C:\TMP\myfile.json" -Force - - .NOTES - File Name : Set-ChallengeFile.ps1 - Author : Marco Blessing - marco.blessing@googlemail.com - Requires : - - .LINK - https://github.com/OCram85/PSCredentialStore - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory = $false)] - [string]$Path = "{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData, - - [Parameter(Mandatory = $false)] - [ValidateSet(16, 24, 32)] - [string]$KeySize = "24", - - [switch]$Force - ) - - if ((Test-Path -Path $Path)) { - if ($Force -eq $true) { - Remove-Item -Path $Path -Confirm:$false -Force - } - else { - Write-Error "The given file already exists!. Use the -Force switch to override it." -ErrorAction Stop - } - } - $PSCredentialStoreDataDir = Split-Path -Path $Path -Parent - if (-not (Test-Path $PSCredentialStoreDataDir)) { - try { - New-Item -ItemType Directory -Path $PSCredentialStoreDataDir - } - catch { - Write-Error ("Could not create the parent data dir {0}" -f $PSCredentialDataDir) -ErrorAction Stop - } - } - try { - $Keys = Get-RandomKey -Size $KeySize - [io.file]::WriteAllBytes($Path, $Keys) - } - catch { - $_.Exception | Format-List -Force | Out-String | Write-Error -ErrorAction Stop - } -} diff --git a/src/ChallengeFile/Test-ChallengeFile.ps1 b/src/ChallengeFile/Test-ChallengeFile.ps1 deleted file mode 100644 index 77eaa06..0000000 --- a/src/ChallengeFile/Test-ChallengeFile.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -function Test-ChallengeFile { - <# - .SYNOPSIS - Simple path check for challenge file needed by the CredentialStores. - - .DESCRIPTION - This is supposed to be a internal function to check the existence for a challenge file. - - .PARAMETER Path - Specify the path to the challenge file. - - .INPUTS - [None] - - .OUTPUTS - [Bool]. - - .EXAMPLE - If (Test-ChallengeFile) { - Write-Host "The file exists." - } - Else { - Write-Warning "Couldn't find the given file!" - } - - .NOTES - File Name : Test-ChallengeFile.ps1 - Author : Marco Blessing - marco.blessing@googlemail.com - Requires : - - .LINK - https://github.com/OCram85/PSCredentialStore - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory = $false)] - [ValidateNotNullOrEmpty()] - [String]$Path = "{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData - ) - - if (Test-Path $Path) { - $true - } - else { - $false - } -} diff --git a/src/Connection/Connect-To.ps1 b/src/Connection/Connect-To.ps1 index f869d22..c39123d 100644 --- a/src/Connection/Connect-To.ps1 +++ b/src/Connection/Connect-To.ps1 @@ -93,15 +93,30 @@ function Connect-To { [Parameter(Mandatory = $False, ParameterSetName = "Private")] [PSCredential]$Credentials, + [Parameter(Mandatory = $true, ParameterSetNAme = "Shared")] + [switch]$Shared, + [Parameter(Mandatory = $False, ParameterSetName = "Shared")] [ValidateNotNullOrEmpty()] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, + [string]$Path, - [Parameter(Mandatory = $false, ParameterSetNAme = "Shared")] - [switch]$Shared + [Parameter(Mandatory = $False, ParameterSetName = "Private")] + [Parameter(Mandatory = $False, ParameterSetName = "Shared")] + [switch]$PassThru ) begin { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath + } + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } + } + # First check the optional modules if (-not (Resolve-Dependency -Name $Type)) { Write-Error -Message ("Could not resolve the optional dependencies defined for {0}" -f $Type) -ErrorAction 'Stop' @@ -118,10 +133,6 @@ function Connect-To { } process { - # Set the correct CredentialStore Path depending on the used ParameterSetName - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA - } if (-not ($Credentials)) { # Load the credential from the CredentialStore. If the credential doesn't exist, we need to # return 1, so a calling if statement can handle the failure detection. @@ -131,16 +142,16 @@ function Connect-To { try { if ($Identifier -ne "") { $RemoteHostIdentifier = "{0}/{1}" -f $Identifier, $RemoteHost - $creds = Get-CredentialStoreItem -RemoteHost $RemoteHostIdentifier -Path $Path + $creds = Get-CredentialStoreItem -Shared -RemoteHost $RemoteHostIdentifier -Path $Path } else { - $creds = Get-CredentialStoreItem -RemoteHost $RemoteHost -Path $Path + $creds = Get-CredentialStoreItem -Shared -RemoteHost $RemoteHost -Path $Path } } catch { $MessageParams = @{ - Message = "Unable to look up credential store item for RemoteHost {0}/Identifier {1}!" -f $RemoteHost, $Identifier + Message = "Unable to look up credential store item for RemoteHost {0}/Identifier {1}!" -f $RemoteHost, $Identifier ErrorAction = "Stop" } Write-Error @MessageParams @@ -152,7 +163,7 @@ function Connect-To { if ($creds.UserName -eq "" -or $creds.Password.GetType().Name -ne "SecureString") { $MessageParams = @{ - Message = "Please provide valid credentials for RemoteHost {0}!" -f $RemoteHost + Message = "Please provide valid credentials for RemoteHost {0}!" -f $RemoteHost ErrorAction = "Stop" } Write-Error @MessageParams @@ -167,7 +178,7 @@ function Connect-To { catch { $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -177,9 +188,9 @@ function Connect-To { # First establish the FTP session $WinSCPConParams = @{ Credential = $creds - Hostname = $RemoteHost - Protocol = 'Ftp' - FtpMode = 'Passive' + Hostname = $RemoteHost + Protocol = 'Ftp' + FtpMode = 'Passive' } try { $FTPSessionOption = New-WinSCPSessionOption @WinSCPConParams @@ -192,7 +203,7 @@ function Connect-To { if (!($WinSCPSession.Opened)) { # Check the connection state and find out if the session is still open. $MessageParams = @{ - Message = "Connection to {0} using Type {1} was established. But now it seems to be lost!" -f $RemoteHost, $Type + Message = "Connection to {0} using Type {1} was established. But now it seems to be lost!" -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -206,7 +217,7 @@ function Connect-To { catch { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -220,7 +231,7 @@ function Connect-To { catch { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -228,13 +239,19 @@ function Connect-To { } "CisServer" { try { - Connect-CisServer -Server $RemoteHost -Credential $creds -ErrorAction Stop | Out-Null + if ($PassThru.IsPresent) { + Connect-CisServer -Server $RemoteHost -Credential $creds -ErrorAction Stop + } + else { + Connect-CisServer -Server $RemoteHost -Credential $creds -ErrorAction Stop | Out-Null + } + } catch { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -243,17 +260,17 @@ function Connect-To { "ExchangeHTTP" { try { $ConnectionParams = @{ - ConnectionURI = "http://{0}/powershell" -f $RemoteHost + ConnectionURI = "http://{0}/powershell" -f $RemoteHost ConfigurationName = 'Microsoft.Exchange' - Credential = $creds - ErrorAction = 'Stop' + Credential = $creds + ErrorAction = 'Stop' } $Global:PSExchangeRemote = New-PSSession @ConnectionParams } catch { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -262,17 +279,17 @@ function Connect-To { "ExchangeHTTPS" { try { $ConnectionParams = @{ - ConnectionURI = "https://{0}/powershell" -f $RemoteHost + ConnectionURI = "https://{0}/powershell" -f $RemoteHost ConfigurationName = 'Microsoft.Exchange' - Credential = $creds - ErrorAction = 'Stop' + Credential = $creds + ErrorAction = 'Stop' } $Global:PSExchangeRemote = New-PSSession @ConnectionParams } catch { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -280,9 +297,9 @@ function Connect-To { } "SCP" { $WinSCPSessionParams = @{ - Credential = $creds - Hostname = $RemoteHost - Protocol = 'Scp' + Credential = $creds + Hostname = $RemoteHost + Protocol = 'Scp' GiveUpSecurityAndAcceptAnySshHostKey = $True } try { @@ -293,7 +310,7 @@ function Connect-To { catch { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -302,7 +319,7 @@ function Connect-To { if (!($WinSCPSession.Opened)) { # Check the connection state and find out if the session is still open. $MessageParams = @{ - Message = "Connection to {0} using Type {1} was established. But now it seems to be lost!" -f $RemoteHost, $Type + Message = "Connection to {0} using Type {1} was established. But now it seems to be lost!" -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams @@ -311,7 +328,7 @@ function Connect-To { default { # Write a error message to the log. $MessageParams = @{ - Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type + Message = "Unable to connect to {0} using Type {1}." -f $RemoteHost, $Type ErrorAction = "Stop" } Write-Error @MessageParams diff --git a/src/Helper/Get-RandomKey.ps1 b/src/Helper/Get-RandomKey.ps1 deleted file mode 100644 index 907160a..0000000 --- a/src/Helper/Get-RandomKey.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -function Get-RandomKey { - <# - .SYNOPSIS - Returns a random key - - .DESCRIPTION - You can use the key for further use with SecureStrings. - - .PARAMETER Size - Define the key size. You can choose between 16, 24 and 32 - - .INPUTS - [None] - - .OUTPUTS - Returns a Random key as [Byte[]] array. - - .EXAMPLE - .\Get-RandomKey -Size 24 - - .NOTES - ``` - File Name : Get-RandomKey.ps1 - Author : Marco Blessing - marco.blessing@googlemail.com - Requires : - ``` - - .LINK - https://github.com/OCram85/PSCredentialStore - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet(16, 24, 32)] - [string]$size - ) - # Init the vars - [Byte[]]$Key = @() - $i = 0 - - while ($i -ne $size) { - $element = Get-Random -Minimum 0 -Maximum 255 - Write-Debug ("The current element is {0}." -f $element) - $Key += $element - $i++ - } - $Key -} diff --git a/src/Item/Get-CredentialStoreItem.ps1 b/src/Item/Get-CredentialStoreItem.ps1 index 52ad70a..dda05b2 100644 --- a/src/Item/Get-CredentialStoreItem.ps1 +++ b/src/Item/Get-CredentialStoreItem.ps1 @@ -41,11 +41,8 @@ function Get-CredentialStoreItem { #> [CmdletBinding(DefaultParameterSetName = "Private")] - [OutputType([System.Management.Automation.PSCredential])] + [OutputType([PSCredential])] param( - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, - [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [Parameter(Mandatory = $true, ParameterSetName = "Private")] [ValidateNotNullOrEmpty()] @@ -56,55 +53,72 @@ function Get-CredentialStoreItem { [ValidateNotNullOrEmpty()] [string]$Identifier, + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] + [switch]$Shared, + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [switch]$Shared + [ValidateNotNullOrEmpty()] + [string]$Path ) - # First set a constand path for private CredentialStore mode. - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA + begin { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath + } + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } + } } - if ($Identifier -ne "") { - $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost - } - else { - $CredentialName = $RemoteHost - } + process { + if ($Identifier -ne "") { + $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost + } + else { + $CredentialName = $RemoteHost + } - if (Test-CredentialStore -Path $Path) { - $CS = Get-CredentialStore -Path $Path - $CSMembers = Get-Member -InputObject $CS - # Let`s first check if the given remote host exists as object property - if (($CSMembers.MemberType -eq "NoteProperty") -and ($CSMembers.Name -eq $CredentialName)) { - if ($CS.Type -eq "Private") { - $CSItem = [ordered]@{ - User = $CS.$CredentialName.User - Password = ConvertTo-SecureString -String $CS.$CredentialName.Password + if (Test-CredentialStore -Shared -Path $Path) { + $CS = Get-CredentialStore -Shared -Path $Path + $CSMembers = Get-Member -InputObject $CS + # Let's first check if the given remote host exists as object property + if (($CSMembers.MemberType -eq "NoteProperty") -and ($CSMembers.Name -contains $CredentialName)) { + $Cert = Get-PfxCertificate -FilePath $CS.PfXCertificate -ErrorAction Stop + $DecryptedKey = $Cert.PrivateKey.Decrypt( + [Convert]::FromBase64String($CS.$CredentialName.EncryptedKey), + [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1 + ) + + if (! $ExpandOutput.isPresent) { + [PSCredential]::new( + $CS.$CredentialName.User, + ($CS.$CredentialName.Password | ConvertTo-SecureString -Key $DecryptedKey) + ) } } else { - $Key = Get-ChallengeFile - $CSItem = [ordered]@{ - User = $CS.$CredentialName.User - Password = ConvertTo-SecureString -String $CS.$CredentialName.Password -Key $Key + $MsgParams = @{ + ErrorAction = "Stop" + Message = "Could not find credentials for the given remote host: {0}" -f $RemoteHost } + Write-Error @MsgParams } - New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $CSItem.User, $CSItem.Password } else { $MsgParams = @{ ErrorAction = "Stop" - Message = "Could not find credentials for the given remote host: {0}" -f $RemoteHost + Message = "The given credential store ({0}) does not exist!" -f $Path } Write-Error @MsgParams } } - else { - $MsgParams = @{ - ErrorAction = "Stop" - Message = "The given credential store ({0}) does not exist!" -f $Path - } - Write-Error @MsgParams + + end { + } + } diff --git a/src/Item/New-CredentialStoreItem.ps1 b/src/Item/New-CredentialStoreItem.ps1 index 9b57f41..56d23c5 100644 --- a/src/Item/New-CredentialStoreItem.ps1 +++ b/src/Item/New-CredentialStoreItem.ps1 @@ -42,9 +42,6 @@ function New-CredentialStoreItem { [CmdletBinding(DefaultParameterSetName = "Private")] param( - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, - [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [Parameter(Mandatory = $true, ParameterSetName = "Private")] [ValidateNotNullOrEmpty()] @@ -59,78 +56,120 @@ function New-CredentialStoreItem { [ValidateNotNullOrEmpty()] [PSCredential]$Credential, + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] + [switch]$Shared, + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [switch]$Shared + [ValidateNotNullOrEmpty()] + [string]$Path + + ) - # First set a constand path for private CredentialStore mode. - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA - } - - # Lets do a quick test on the given CredentialStore. - if (-not(Test-CredentialStore -Path $Path)) { - $MessageParams = @{ - Message = "Could not add anything into the given CredentailStore." - ErrorAction = "Stop" + begin { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath } - Write-Error @MessageParams - } - - # Read the file content based on the given ParameterSetName - $CSContent = Get-CredentialStore -Path $Path - - $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" - - if ($Identifier -ne "") { - $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost - } - else { - $CredentialName = $RemoteHost - } - - if (-not($Credential)) { - $Credential = Get-Credential -Message $CredentialName - } - - if ($Credential.UserName) { - if ($CSContent.Type -eq "Shared") { - $Key = Get-ChallengeFile - $encypted = ConvertFrom-SecureString -SecureString $Credential.Password -Key $Key + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } } - else { - $encypted = ConvertFrom-SecureString -SecureString $Credential.Password - } - if (Get-Member -InputObject $CSContent -Name $CredentialName -Membertype Properties) { + } + + process { + # Lets do a quick test on the given CredentialStore. + if (-not(Test-CredentialStore -Shared -Path $Path)) { $MessageParams = @{ - Message = "The given host already exists. Nothing to do here." + Exception = [System.IO.FileNotFoundException]::new( + 'Could not add anything into the given CredentialStore.' + ) + ErrorAction = "Stop" } - Write-Warning @MessageParams + Write-Error @MessageParams + } + + # Read the file content based on the given ParameterSetName + <# + if ($PSCmdlet.ParameterSetName -eq 'Private') { + $CSContent = Get-CredentialStore + } + elseif ($PSCmdlet.ParameterSetName -eq 'Shared') { + $CSContent = Get-CredentialStore -Shared -Path $Path + } + #> + $CSContent = Get-CredentialStore -Shared -Path $Path + + $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" + + if ($Identifier -ne "") { + $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost } else { - $CredentialHash = [ordered]@{ - User = $Credential.UserName - Password = $encypted - Creation = $CurrentDate - } - Add-Member -InputObject $CSContent -Name $CredentialName -MemberType NoteProperty -Value $CredentialHash + $CredentialName = $RemoteHost + } + + if (-not($Credential)) { + $Credential = Get-Credential -Message $CredentialName + } + + if ($Credential.UserName) { try { - ConvertTo-Json -InputObject $CSContent | Out-File -FilePath $Path + $Cert = Get-PfxCertificate -FilePath $CSContent.PfxCertificate -ErrorAction Stop } - catch [System.Exception] { - $MessageParams = @{ - Message = "Couldn't add item into credential store!" - ErrorAction = "Stop" + catch { + $_.Exception.Message | Write-Error + $ErrorParams = @{ + ErrorAction = 'Stop' + Exception = [System.Security.Cryptography.CryptographicException]::new( + 'Could not read the given PFX certificate.' + ) + } + Write-Error @ErrorParams + } + + if (Get-Member -InputObject $CSContent -Name $CredentialName -Membertype Properties) { + $MessageParams = @{ + Message = "The given host already exists. Nothing to do here." + } + Write-Warning @MessageParams + } + else { + $RSAKey = Get-RandomAESKey + + $CredentialHash = [ordered]@{ + User = $Credential.UserName + Password = ConvertFrom-SecureString -SecureString $Credential.Password -Key $RSAKey + Created = $CurrentDate + LastChange = $null + EncryptedKey = [Convert]::ToBase64String($Cert.PublicKey.Key.Encrypt($RSAKey, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)) + } + Add-Member -InputObject $CSContent -Name $CredentialName -MemberType NoteProperty -Value $CredentialHash + try { + ConvertTo-Json -InputObject $CSContent | Out-File -FilePath $Path + } + catch { + $MessageParams = @{ + Message = "Couldn't add item into credential store!" + ErrorAction = "Stop" + } + Write-Error @MessageParams } - Write-Error @MessageParams } } - } - else { - $MessageParams = @{ - Message = "Please Provide at least a valid user!" - ErrorAction = "Stop" + else { + $MessageParams = @{ + Message = "Please Provide at least a valid user!" + ErrorAction = "Stop" + } + Write-Error @MessageParams } - Write-Error @MessageParams } + + end { + + } + } diff --git a/src/Item/Remove-CredentialStoreItem.ps1 b/src/Item/Remove-CredentialStoreItem.ps1 index 00f21b2..0ea0b45 100644 --- a/src/Item/Remove-CredentialStoreItem.ps1 +++ b/src/Item/Remove-CredentialStoreItem.ps1 @@ -27,8 +27,16 @@ function Remove-CredentialStoreItem { [None] .EXAMPLE - Remove-CredentialStoreItem -Path "C:\TMP\mystore.json" -RemoteHost "esx01.myside.local" - Remove-CredentialStoreItem -Path "C:\TMP\mystore.json" -RemoteHost "esx01.myside.local" -Identifier svc + Remove-CredentialStoreItem -RemoteHost "esx01.myside.local" + + .EXAMPLE + Remove-CredentialStoreItem -Shared -RemoteHost "esx01.myside.local" + + .EXAMPLE + Remove-CredentialStoreItem -Shared -Path "C:\TMP\mystore.json" -RemoteHost "esx01.myside.local" + + .EXAMPLE + Remove-CredentialStoreItem -RemoteHost "esx01.myside.local" -Identifier svc .NOTES ``` @@ -43,9 +51,6 @@ function Remove-CredentialStoreItem { [CmdletBinding(DefaultParameterSetName = "Private")] param( - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, - [Parameter(Mandatory = $true, ParameterSetName = "Private")] [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [string]$RemoteHost, @@ -54,43 +59,62 @@ function Remove-CredentialStoreItem { [Parameter(Mandatory = $false, ParameterSetName = "Shared")] [string]$Identifier, + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] + [switch]$Shared, + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [switch]$Shared + [ValidateNotNullOrEmpty()] + [string]$Path ) - # First set a constand path for private CredentialStore mode. - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA - } - - # Lets do a quick test on the given CredentialStore. - if (-not(Test-CredentialStore -Path $Path)) { - $MessageParams = @{ - Message = "Could not add anything into the given CredentailStore." - ErrorAction = "Stop" + begin { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath } - Write-Error @MessageParams - } - - # Read the file content based on the given ParameterSetName - $CSContent = Get-CredentialStore -Path $Path - - if ($Identifier -ne "") { - $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost - } - else { - $CredentialName = $RemoteHost - } - - if (Get-Member -InputObject $CSContent -Name $CredentialName -Membertype Properties) { - # We need to use the .NET Method because there is no easier way in PowerShell. - $CSContent.PSObject.Properties.Remove($CredentialName) - ConvertTo-Json -InputObject $CSContent | Out-File -FilePath $Path - } - else { - $MessageParams = @{ - Message = "The given CredentailStoreItem does not exist." + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } } - Write-Warning @MessageParams } + + process { + # Lets do a quick test on the given CredentialStore. + if (-not(Test-CredentialStore -Shared -Path $Path)) { + $MessageParams = @{ + Message = "Could not add anything into the given CredentialStore." + ErrorAction = "Stop" + } + Write-Error @MessageParams + } + + # Read the file content based on the given ParameterSetName + $CSContent = Get-CredentialStore -Shared -Path $Path + + if ($Identifier -ne "") { + $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost + } + else { + $CredentialName = $RemoteHost + } + + if (Get-Member -InputObject $CSContent -Name $CredentialName -Membertype NoteProperty) { + # We need to use the .NET Method because there is no easier way in PowerShell. + $CSContent.PSObject.Properties.Remove($CredentialName) + ConvertTo-Json -InputObject $CSContent -Depth 5 | Out-File -FilePath $Path -Encoding utf8 + } + else { + $MessageParams = @{ + Message = "The given CredentialStoreItem does not exist." + } + Write-Warning @MessageParams + } + } + + end { + + } + } diff --git a/src/Item/Set-CredentialStoreItem.ps1 b/src/Item/Set-CredentialStoreItem.ps1 index 3a5b4cb..9b3418e 100644 --- a/src/Item/Set-CredentialStoreItem.ps1 +++ b/src/Item/Set-CredentialStoreItem.ps1 @@ -42,9 +42,6 @@ function Set-CredentialStoreItem { [CmdletBinding(DefaultParameterSetName = "Private")] param( - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, - [Parameter(Mandatory = $true, ParameterSetName = "Private")] [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [string]$RemoteHost, @@ -57,66 +54,91 @@ function Set-CredentialStoreItem { [ValidateNotNullOrEmpty()] [PSCredential]$Credential, + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] + [switch]$Shared, + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [switch]$Shared + [ValidateNotNullOrEmpty()] + [string]$Path ) - # First set a constant path for private CredentialStore mode. - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA - } - - # Lets do a quick test on the given CredentialStore. - if (-not(Test-CredentialStore -Path $Path)) { - $MessageParams = @{ - Message = "Could not add anything into the given CredentailStore." - ErrorAction = "Stop" + begin { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath } - Write-Error @MessageParams - } - - # Read the file content based on the given ParameterSetName - $CSContent = Get-CredentialStore -Path $Path - - $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" - - if ($Identifier -ne "") { - $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost - } - else { - $CredentialName = $RemoteHost - } - - if (-not($Credential)) { - $Credential = Get-Credential -Message $CredentialName - } - - if ($Credential.UserName) { - if ($CSContent.Type -eq "Shared") { - $Key = Get-ChallengeFile - $encypted = ConvertFrom-SecureString -SecureString $Credential.Password -Key $Key - } - else { - $encypted = ConvertFrom-SecureString -SecureString $Credential.Password - } - if (Get-Member -InputObject $CSContent -Name $CredentialName -Membertype Properties) { - $CSContent.$CredentialName.User = $Credential.UserName - $CSContent.$CredentialName.Password = $encypted - $CSContent.$CredentialName.Creation = $CurrentDate - ConvertTo-Json -InputObject $CSContent | Out-File -FilePath $Path - } - else { - $MessageParams = @{ - Message = "The given CredentailStoreItem does not exist." + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared } - Write-Warning @MessageParams } } - Else { - $MessageParams = @{ - Message = "Please Provide at least a valid user!" - ErrorAction = "Stop" + + process { + # Lets do a quick test on the given CredentialStore. + if (-not(Test-CredentialStore -Shared -Path $Path)) { + $MessageParams = @{ + Message = "Could not add anything into the given CredentailStore." + ErrorAction = "Stop" + } + Write-Error @MessageParams } - Write-Error @MessageParams + + # Read the file content based on the given ParameterSetName + $CSContent = Get-CredentialStore -Shared -Path $Path + + $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" + + if ($Identifier -ne "") { + $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost + } + else { + $CredentialName = $RemoteHost + } + + if (-not($Credential)) { + $Credential = Get-Credential -Message $CredentialName + } + + if ($Credential.UserName) { + try { + $Cert = Get-PfxCertificate -FilePath $CSContent.PfxCertificate -ErrorAction Stop + } + catch { + $_.Exception.Message | Write-Error + $ErrorParams = @{ + Message = 'Could not read the given PFX certificate.' + ErrorAction = 'Stop' + Exception = [System.Security.Cryptography.CryptographicException]::new() + } + Write-Error @ErrorParams + } + + if (Get-Member -InputObject $CSContent -Name $CredentialName -Membertype Properties) { + $RSAKey = Get-RandomAESKey + $CSContent.$CredentialName.User = $Credential.UserName + $CSContent.$CredentialName.Password = ConvertFrom-SecureString -SecureString $Credential.Password -Key $RSAKey + $CSContent.$CredentialName.LastChange = $CurrentDate + $CSContent.$CredentialName.EncryptedKey = [Convert]::ToBase64String( + $Cert.PublicKey.Key.Encrypt( + $RSAKey, + [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1 + ) + ) + ConvertTo-Json -InputObject $CSContent -Depth 5 | Out-File -FilePath $Path -Encoding utf8 + } + } + Else { + $MessageParams = @{ + Message = "Please Provide at least a valid user!" + ErrorAction = "Stop" + } + Write-Error @MessageParams + } + } + + end { + } } diff --git a/src/Item/Test-CredentialStoreItem.ps1 b/src/Item/Test-CredentialStoreItem.ps1 index 692befb..5f125e7 100644 --- a/src/Item/Test-CredentialStoreItem.ps1 +++ b/src/Item/Test-CredentialStoreItem.ps1 @@ -1,4 +1,4 @@ -function Test-CredentialStoreItem() { +function Test-CredentialStoreItem { <# .SYNOPSIS Checks if the given RemoteHost identifier combination exists in the credential store. @@ -64,32 +64,48 @@ function Test-CredentialStoreItem() { [switch]$Shared ) - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA + begin { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath + } + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } + } } - if ($Identifier -ne "") { - $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost - } - else { - $CredentialName = $RemoteHost - } - - if (Test-CredentialStore -Path $Path) { - $CS = Get-CredentialStore -Path $Path - $CSMembers = Get-Member -InputObject $CS - if (($CSMembers.MemberType -eq "NoteProperty") -and ($CSMembers.Name -eq $CredentialName)) { - return $true + process { + if ($Identifier -ne "") { + $CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost } else { - return $false + $CredentialName = $RemoteHost + } + + if (Test-CredentialStore -Shared -Path $Path) { + $CS = Get-CredentialStore -Shared -Path $Path + $CSMembers = Get-Member -InputObject $CS + if (($CSMembers.MemberType -eq "NoteProperty") -and ($CSMembers.Name -contains $CredentialName)) { + return $true + } + else { + return $false + } + } + else { + $MsgParams = @{ + ErrorAction = "Stop" + Message = "The given credential store ({0}) does not exist!" -f $Path + } + Write-Error @MsgParams } } - else { - $MsgParams = @{ - ErrorAction = "Stop" - Message = "The given credential store ({0}) does not exist!" -f $Path - } - Write-Error @MsgParams + + end { + } + } diff --git a/src/PSCredentialStore.psd1 b/src/PSCredentialStore.psd1 index abf44b3..7757afc 100644 --- a/src/PSCredentialStore.psd1 +++ b/src/PSCredentialStore.psd1 @@ -1,45 +1,36 @@ -# -# Module manifest for module 'PSCredentialStore' -# -# Generated by: OCram85 -# -# Generated on: 27.07.2017 -# - @{ # Script module or binary module file associated with this manifest. - RootModule = 'PSCredentialStore' + RootModule = 'PSCredentialStore.psm1' # Version number of this module. - # Do not touch the version number. It gets replaced in the build process. - ModuleVersion = '0.0.0.9999' + ModuleVersion = '0.0.9999' # Supported PSEditions - # CompatiblePSEditions = @() + CompatiblePSEditions = 'Desktop', 'Core' # ID used to uniquely identify this module - GUID = '6800e192-9df8-4e30-b253-eb2c799bbe84' + GUID = '6800e192-9df8-4e30-b253-eb2c799bbe84' # Author of this module - Author = 'OCram85' + Author = 'OCram85' # Company or vendor of this module - CompanyName = '' + CompanyName = '' # Copyright statement for this module - Copyright = '(c) 2017 OCram85. All rights reserved.' + Copyright = '(c) 2019 OCram85. All rights reserved.' # Description of the functionality provided by this module - Description = 'A simple credential manager to store and reuse multiple credential objects.' + Description = 'A simple credential manager to store and reuse multiple credential objects.' - # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '4.0' + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.1' - # Name of the Windows PowerShell host required by this module + # Name of the PowerShell host required by this module # PowerShellHostName = '' - # Minimum version of the Windows PowerShell host required by this module + # Minimum version of the PowerShell host required by this module # PowerShellHostVersion = '' # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. @@ -70,32 +61,36 @@ # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @( - # Connection Group + FunctionsToExport = @( + # Certificate + 'New-CRTAttribute', + 'New-PfxCertificate', + 'Use-PfxCertificate', + # Connection 'Connect-To', 'Disconnect-From', 'Test-CSConnection', - # Item Group + # Item 'Get-CredentialStoreItem', - 'Set-CredentialStoreItem', 'New-CredentialStoreItem', 'Remove-CredentialStoreItem', + 'Set-CredentialStoreItem', 'Test-CredentialStoreItem', - # Store Group + # Store 'Get-CredentialStore', 'New-CredentialStore', - 'Test-CredentialStore' - + 'Test-CredentialStore', + 'Update-CredentialStore' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = @() + CmdletsToExport = @() # Variables to export from this module - VariablesToExport = '*' + VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - AliasesToExport = @() + AliasesToExport = @() # DSC resources to export from this module # DscResourcesToExport = @() @@ -107,33 +102,40 @@ # 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 = @{ + PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('CredentialStore', - 'CredentialManager' - ) + Tags = 'CredentialStore', 'CredentialManager' # A URL to the license for this module. - LicenseUri = 'https://github.com/OCram85/PSCredentialStore/blob/master/LICENSE' + LicenseUri = 'https://github.com/OCram85/PSCredentialStore/blob/master/LICENSE' # A URL to the main website for this project. - ProjectUri = 'https://github.com/OCram85/PSCredentialStore' + ProjectUri = 'https://github.com/OCram85/PSCredentialStore' # A URL to an icon representing this module. - # IconUri = '' + IconUri = 'https://raw.githubusercontent.com/OCram85/PSCredentialStore/master/assets/logo256.png' # ReleaseNotes of this module ReleaseNotes = 'This is a pre-release version!. Do not use in production!' + # Prerelease string of this module + Prerelease = 'alpha1' + + # Flag to indicate whether the module requires explicit user acceptance for install/update + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + } # End of PSData hashtable } # End of PrivateData hashtable # HelpInfo URI of this module - HelpInfoURI = 'https://github.com/OCram85/PSCredentialStore' + HelpInfoURI = 'https://github.com/OCram85/PSCredentialStore' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' diff --git a/src/PSCredentialStore.psm1 b/src/PSCredentialStore.psm1 index 61866b5..21ddd38 100644 --- a/src/PSCredentialStore.psm1 +++ b/src/PSCredentialStore.psm1 @@ -1,10 +1,18 @@ -$Items = (Get-ChildItem -Path ("{0}\*.ps1" -f $PSScriptRoot ) -Recurse ).FullName | Where-Object { +#region module-definition + +#endregion module-definition +Set-Variable -Name "CSVersion" -Value "2.0.0" -Option Constant -Scope 'Script' -ErrorAction Stop + + + +#region dot-sourcing +# dot-sourcing all module functions. The export is handled via manifest file. + +$Items = (Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath '*.ps1') -Recurse ).FullName | Where-Object { $_ -notmatch "(Classes|Init)" } -ForEach ($Item in $Items) { +foreach ($Item in $Items) { # Write-Verbose ("dot sourcing file {0}" -f $Item) . $Item } - -# Exports are now controlled by module manifest -# Export-ModuleMember -Function * +#endregion dot-sourcing diff --git a/src/Private/Get-DefaultCredentialStorePath.ps1 b/src/Private/Get-DefaultCredentialStorePath.ps1 new file mode 100644 index 0000000..4d9d162 --- /dev/null +++ b/src/Private/Get-DefaultCredentialStorePath.ps1 @@ -0,0 +1,61 @@ +function Get-DefaultCredentialStorePath { + <# + .SYNOPSIS + Returns the default CredentialStore path based on the current OS. + + .DESCRIPTION + This is a low level helper function. + + .INPUTS + [None] + + .OUTPUTS + [string] + + .EXAMPLE + $Path = Get-DefaultCredentialStorePath + + .NOTES + File Name : Get-DefaultCredentialStorePath.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $false)] + [switch]$Shared + ) + + begin {} + + process { + if ($Shared.IsPresent) { + if ($IsLinux) { + return Join-Path -Path '/var/opt' -ChildPath 'PSCredentialStore/CredentialStore.json' + } + if ($IsMacOS) { + return Join-Path -Path '/var/opt' -ChildPath 'PSCredentialStore/CredentialStore.json' + } + elseif (($isWindows) -or ($PSVersionTable.PSVersion.Major -lt 6) -or ($PSVersionTable.PSEdition -eq 'Desktop')) { + return Join-Path -Path $env:ProgramData -ChildPath 'PSCredentialStore/CredentialStore.json' + } + } + else { + if ($IsLinux) { + return Join-Path -Path $Env:HOME -ChildPath 'CredentialStore.json' + } + if ($IsMacOS) { + return Join-Path -Path $Env:HOME -ChildPath 'CredentialStore.json' + } + elseif (($isWindows) -or ($PSVersionTable.PSVersion.Major -lt 6) -or ($PSVersionTable.PSEdition -eq 'Desktop')) { + return Join-Path -Path $env:AppData -ChildPath 'CredentialStore.json' + } + } + } + + end {} +} diff --git a/src/Helper/Get-ModuleBase.ps1 b/src/Private/Get-ModuleBase.ps1 similarity index 100% rename from src/Helper/Get-ModuleBase.ps1 rename to src/Private/Get-ModuleBase.ps1 diff --git a/src/Private/Get-RandomAESKey.ps1 b/src/Private/Get-RandomAESKey.ps1 new file mode 100644 index 0000000..27baae5 --- /dev/null +++ b/src/Private/Get-RandomAESKey.ps1 @@ -0,0 +1,44 @@ +function Get-RandomAESKey { + <# + .SYNOPSIS + Generate a new 32-byte AES key. + + .DESCRIPTION + Uses the System.Security.Cryptography namespace for random aes key generation. + + .INPUTS + [None] + + .OUTPUTS + [byte[]] + + .EXAMPLE + .\Get-RandomAESKey + + .NOTES + File Name : Get-RandomAESKey.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + + [CmdletBinding()] + [OutputType([byte[]])] + param() + + begin {} + + process { + $key = [byte[]]::new(32) + $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create() + $rng.GetBytes($key) + Write-Output $key + if ($null -ne $key) { + [array]::Clear($key, 0, $key.Length) + } + + } + end {} +} diff --git a/src/Private/Get-TempDir.ps1 b/src/Private/Get-TempDir.ps1 new file mode 100644 index 0000000..c0b1580 --- /dev/null +++ b/src/Private/Get-TempDir.ps1 @@ -0,0 +1,44 @@ +function Get-TempDir { + <# + .SYNOPSIS + Returns the valid temp dir of the current OS + + .DESCRIPTION + Returns the valid temp dir of the current OS. + + .INPUTS + [None] + .OUTPUTS + [string] + + .EXAMPLE + Get-TempDir + + .NOTES + File Name : Get-TempDir.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + [CmdletBinding()] + [OutputType([string])] + param() + begin { + + } + process { + if ($IsLinux) { + return (Resolve-Path -Path '/tmp/').Path + } + if ($IsMacOS) { + return (Resolve-Path -Path '/tmp/').Path + } + elseif (($isWindows) -or ($PSVersionTable.PSVersion.Major -lt 6) -or ($PSVersionTable.PSEdition -eq 'Desktop')) { + return (Resolve-Path -Path $env:TEMP).Path + } + } + end { + } +} diff --git a/src/Helper/Resolve-Dependency.ps1 b/src/Private/Resolve-Dependency.ps1 similarity index 100% rename from src/Helper/Resolve-Dependency.ps1 rename to src/Private/Resolve-Dependency.ps1 diff --git a/src/Helper/Test-Module.ps1 b/src/Private/Test-Module.ps1 similarity index 58% rename from src/Helper/Test-Module.ps1 rename to src/Private/Test-Module.ps1 index f116459..2872db3 100644 --- a/src/Helper/Test-Module.ps1 +++ b/src/Private/Test-Module.ps1 @@ -35,7 +35,7 @@ function Test-Module { .NOTES ``` - File Name : Get-RandomKey.ps1 + File Name : Test-Module.ps1 Author : Marco Blessing - marco.blessing@googlemail.com Requires : ``` @@ -50,10 +50,6 @@ function Test-Module { [ValidateNotNullOrEmpty()] [string]$Name, - [Parameter(Mandatory = $false)] - [ValidateSet('Module', 'PSSnapin', 'Custom')] - [string]$Type = 'Module', - [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$MessagePattern = @" @@ -62,45 +58,22 @@ Could not find the required {0} called {1}. Please install the required {0} to r [Parameter(Mandatory = $false)] [switch]$StopIfFails ) - begin { - - } + begin {} process { $Message = $MessagePattern -f $Type, $Name Write-Debug $Message - switch ($Type) { - 'Module' { - if (Get-Module -Name $Name -ListAvailable) { - return $true - } - else { - if ($StopIfFails) { - Write-Error -Message $Message -ErrorAction Stop -Category NotInstalled - } - return $false - } - } - 'PSSnapin' { - if (Get-PSSnapin -Name $Name -Registered -ErrorAction SilentlyContinue) { - return $true - } - else { - if ($StopIfFails) { - Write-Error -Message $Message -ErrorAction Stop -Category NotInstalled - } - return $false - } - } - - 'Custom' { - Throw 'Custom tests are not implemented yet!' + if (Get-Module -Name $Name -ListAvailable) { + return $true + } + else { + if ($StopIfFails) { + Write-Error -Message $Message -ErrorAction Stop -Category NotInstalled } + return $false } } - end { - - } + end {} } diff --git a/src/Store/Get-CredentialStore.ps1 b/src/Store/Get-CredentialStore.ps1 index 8e26930..20191ff 100644 --- a/src/Store/Get-CredentialStore.ps1 +++ b/src/Store/Get-CredentialStore.ps1 @@ -35,36 +35,54 @@ function Get-CredentialStore { #> [CmdletBinding(DefaultParameterSetName = "Private")] + [OutputType("PSCredentialStore.Store")] param( [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] + [string]$Path, + + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [switch]$Shared ) - if ($PSCmdlet.ParameterSetName -eq 'Private') { - $Path = "{0}\CredentialStore.json" -f $env:APPDATA - } + begin {} - if (Test-CredentialStore -Path $Path) { - try { - $FileContent = Get-Content -Path $Path -Raw - ConvertFrom-Json $FileContent + process { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath } - catch [System.Exception] { + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } + } + + if (Test-CredentialStore -Path $Path -Shared) { + try { + $FileContent = Get-Content -Path $Path -Raw + $CS = ConvertFrom-Json $FileContent + $CS.PSObject.TypeNames.Insert(0, "PSCredentialStore.Store") + return $CS + } + catch [System.Exception] { + $MessageParams = @{ + Message = "Unknown CredentialStore format. Invalid JSON file." + ErrorAction = "Stop" + } + Write-Error @MessageParams + } + } + else { $MessageParams = @{ - Message = "Unknown CredentialStore format. Invalid JSON file." + Message = "Could not find the CredentialStore." ErrorAction = "Stop" } Write-Error @MessageParams } } - else { - $MessageParams = @{ - Message = "Could not find the CredentialStore." - ErrorAction = "Stop" - } - Write-Error @MessageParams - } + + end {} + } diff --git a/src/Store/New-CredentialStore.ps1 b/src/Store/New-CredentialStore.ps1 index a688f2d..9eb773e 100644 --- a/src/Store/New-CredentialStore.ps1 +++ b/src/Store/New-CredentialStore.ps1 @@ -22,7 +22,8 @@ function New-CredentialStore { [None] .OUTPUTS - [None] + ['PSCredentialStore.Store'] Returns the recently created CredentialStore object if the -PassThru parameter + was given. .EXAMPLE New-CredentialStore @@ -50,63 +51,163 @@ function New-CredentialStore { https://github.com/OCram85/PSCredentialStore #> - [CmdletBinding(DefaultParameterSetName = "Private")] + [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = "Private")] + [OutputType("PSCredentialStore.Store")] param( - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [switch]$Shared, [Parameter(Mandatory = $false, ParameterSetName = "Shared")] [ValidateNotNullOrEmpty()] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, + [string]$Path, [Parameter(Mandatory = $false, ParameterSetName = "Private")] [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [switch]$Force + [switch]$Force, + + [Parameter(Mandatory = $false, ParameterSetName = "Private")] + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] + [switch]$PassThru, + + [Parameter(Mandatory = $false, ParameterSetName = "Private")] + [Parameter(Mandatory = $false, ParameterSetName = "Shared")] + [Switch]$SkipPFXCertCreation ) - # Lets get the current Date in a human readable format. - $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" + begin { + # Lets get the current Date in a human readable format. + $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" - # Set latest Credential Store version - Set-Variable -Name "CSVersion" -Value "1.2.0" -Option Constant - - # Set the CredentialStore path for private mode. - Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $Env:APPDATA + # Set latest Credential Store version + # Set-Variable -Name "CSVersion" -Value "2.0.0" -Option Constant -Scope } - # Test if in the given store already a CredentialStore exists. - Write-Verbose "Test if there is already a credential store." - if ((Test-CredentialStore -Path $Path) -and ($Force -ne $true)) { - $MessageParam = @{ - Message = "The given file already exists. Use the 'Force' switch to override the existing store." - ErrorAction = "Stop" + process { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath } - Write-Error @MessageParam - } - # We need to use the IDictionary to keep the property sorting in the object. - $ObjProperties = [ordered]@{ - Version = $CSVersion - Creation = $CurrentDate - } - if ($PSCmdlet.ParameterSetName -eq "Shared") { - $ObjProperties.Type = "Shared" - # Check if a ChallengeFile already exists. We don't want to overide it. - # Otherwise previous created CredentialStores couln't be decrypted anymore. - if (-not (Test-ChallengeFile)) { - Set-ChallengeFile + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } + } + + # Test if in the CredentialStore already exists. + Write-Verbose "Test if there is already a credential store." + if ((Test-Path -Path $Path) -and ($Force -ne $true)) { + $ErrorParams = @{ + ErrorAction = 'Stop' + Exception = [System.InvalidOperationException]::new( + 'The given file already exists. Use the -Force switch to override the existing store.' + ) + } + Write-Error @ErrorParams + } + + if (! $SkipPFXCertCreation.IsPresent) { + $CRTParams = @{ + Country = 'DE' + State = 'PSCredentialStore' + City = 'PSCredentialStore' + Organization = 'PSCredentialStore' + OrganizationalUnitName = ' ' + CommonName = 'PrivateStore' + } + $CRTAttribute = New-CRTAttribute @CRTParams + + # If we are working with a ne shared store we have to create the location first. + # Otherwise openssl fails with unknown path + + $StoreHome = Split-Path -Path $Path -Parent + if (! (Test-Path -Path $StoreHome)) { + New-Item -ItemType Directory -Path $StoreHome -ErrorAction Stop + } + + $PfxParams = @{ + CRTAttribute = $CRTAttribute + KeyName = Join-Path -Path $StoreHome -ChildPath 'private.key' + CertName = Join-Path -Path $StoreHome -ChildPath 'PSCredentialStore.pfx' + ErrorAction = 'Stop' + Confirm = $false + } + + if ((Test-Path $PfxParams.CertName) -and (! $Force.IsPresent)) { + $ErrorParams = @{ + Exception = [System.IO.InvalidDataException]::new( + 'There is already a PfxCertificate for a private CredentialStore!' + ) + ErrorAction = 'Stop' + } + Write-Error @ErrorParams + } + + try { + New-PfxCertificate @PfxParams + } + catch { + $_.Exception.Message | Write-Error + $ErrorParams = @{ + ErrorAction = 'Stop' + Exception = [System.Exception]::new( + 'Could not create the private PfXCertificate!' + ) + } + Write-Error @ErrorParams + } + + try { + $FreshCert = Get-PfxCertificate -FilePath $PfxParams.CertName -ErrorAction Stop + } + catch [System.Management.Automation.ItemNotFoundException] { + $_.Exception.Message | Write-Error + Write-Error -Message 'Could not read the new PfxCertificate.' -ErrorAction Stop + } + } + + # We need to use the IDictionary to keep the property sorting in the object. + $ObjProperties = [ordered]@{ + PSTypeName = 'PSCredentialStore.Store' + Version = $CSVersion + Created = $CurrentDate + PfxCertificate = $null + Thumbprint = $null + Type = $null + } + if (! $SkipPFXCertCreation.IsPresent) { + $ObjProperties.PfXCertificate = $PfxParams.CertName + $ObjProperties.Thumbprint = $FreshCert.Thumbprint + } + + if ($PSCmdlet.ParameterSetName -eq "Shared") { + $ObjProperties.Type = "Shared" + } + else { + $ObjProperties.Type = "Private" + } + + $CredentialStoreObj = [PSCustomObject]$ObjProperties + try { + $JSON = ConvertTo-Json -InputObject $CredentialStoreObj -ErrorAction Stop + $JSON | Out-File -FilePath $Path -ErrorAction Stop -Force + } + catch { + $_.Exception.Message | Write-Error + $ErrorParams = @{ + ErrorAction = 'Stop' + Exception = [System.IO.IOException]::new( + 'Unable to convert or write the CredentialStore' + ) + } + Write-Error @ErrorParams + } + + if ($PassThru.IsPresent) { + return $CredentialStoreObj } } - else { - $ObjProperties.Type = "Private" - } - # Create a new object for easy conversion into a json file - $CredentialStoreObj = New-Object -TypeName psobject -Property $ObjProperties - try { - ConvertTo-Json -InputObject $CredentialStoreObj | Out-File -FilePath $Path - } - catch [System.Exception] { - $_.Exception | Format-List -Force | Out-String | Write-Error -ErrorAction Stop + + end { } } diff --git a/src/Store/Test-CredentialStore.ps1 b/src/Store/Test-CredentialStore.ps1 index 34101c3..3b62aca 100644 --- a/src/Store/Test-CredentialStore.ps1 +++ b/src/Store/Test-CredentialStore.ps1 @@ -26,37 +26,40 @@ function Test-CredentialStore { [CmdletBinding(DefaultParameterSetName = "Private")] param( [Parameter(Mandatory = $false, ParameterSetName = "Shared")] - [string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, + [string]$Path, - [Parameter(Mandatory = $false, ParameterSetName = "Shared")] + [Parameter(Mandatory = $true, ParameterSetName = "Shared")] [switch]$Shared ) - - if ($PSCmdlet.ParameterSetName -eq "Private") { - $Path = "{0}\CredentialStore.json" -f $Env:APPDATA + begin { + # Set latest Credential Store version + #Set-Variable -Name "CSVersion" -Value "2.0.0" -Option Constant } - # Set latest Credential Store version - Set-Variable -Name "CSVersion" -Value "1.2.0" -Option Constant - - if (Test-Path $Path) { - Write-Verbose "CredentialStore in given path found." - - # try tor read the store. Removed the Get-CredentialStore function to avoid recursive calls. - try { - $FileContent = Get-Content -Path $Path -Raw - $CSContent = ConvertFrom-Json $FileContent + process { + # Set the CredentialStore for private, shared or custom mode. + Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) + if ($PSCmdlet.ParameterSetName -eq "Private") { + $Path = Get-DefaultCredentialStorePath } - catch { - Write-Warning "Could not read or convert the given CredentialStore." - Return $False + elseif ($PSCmdlet.ParameterSetName -eq "Shared") { + if (!($PSBoundParameters.ContainsKey('Path'))) { + $Path = Get-DefaultCredentialStorePath -Shared + } } - Return $True + Write-Verbose -Message ("Path is: {0}" -f $Path) + if (Test-Path $Path) { + Write-Verbose "CredentialStore in given path found." + return $true + } + else { + Write-Verbose "The given CredentialStore does not exist!" + return $false + } } - Else { - Write-Verbose "The given CredentialStore does not exist!" - Return $False - } + + end {} + } diff --git a/src/Store/Update-CredentialStore.ps1 b/src/Store/Update-CredentialStore.ps1 new file mode 100644 index 0000000..e85d7e7 --- /dev/null +++ b/src/Store/Update-CredentialStore.ps1 @@ -0,0 +1,140 @@ +function Update-CredentialStore { + <# + .SYNOPSIS + A brief description of the function or script. + + .DESCRIPTION + Describe the function of the script using a single sentence or more. + + .PARAMETER One + Description of the Parameter (what it does) + + .INPUTS + Describe the script input parameters (if any), otherwise it may also list the word "[None]". + + .OUTPUTS + Describe the script output parameters (if any), otherwise it may also list the word "[None]". + + .EXAMPLE + .\Remove-Some-Script.ps1 -One content + + .NOTES + File Name : Update-CredentialStore.ps1 + Author : Marco Blessing - marco.blessing@googlemail.com + Requires : + + .LINK + https://github.com/OCram85/PSCredentialStore + #> + [CmdletBinding()] + [OutputType()] + param( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [Version]$From = '1.2.0', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [Version]$To = '2.0.0', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Path, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$PfxCertificate + + ) + begin { + } + process { + if (Test-Path -Path $Path) { + $CSOld = Get-CredentialStore -Shared -Path $Path -ErrorAction Stop + if ($CSOld.Version -ne $From) { + $ErrorParams = @{ + Message = 'Can not migrate CredentialStore from version {0} to {1}' -f $From, $To + ErrorAction = 'Stop' + Exception = [System.Exception]::new() + } + Write-Error @ErrorParams + } + + $CSNew = [PSCustomObject]@{ + PSTypeName = 'PSCredentialStore.Store' + Version = $To + Created = $CurrentDate + PfxCertificate = $null + Thumbprint = $null + Type = $null + } + + if ($PWD -eq (Get-DefaultCredentialStorePath)) { + $CSNew.Type = 'Private' + } + elseif ($PWD -eq (Get-DefaultCredentialStorePath -Shared)) { + $CSNew.Type = 'Shared' + } + else { + $ErrorParams = @{ + Message = 'Can not determine a valid CredentialStore Type!' + ErrorAction = 'Stop' + Exception = [System.Exception]::new() + } + Write-Error @ErrorParams + } + $Cert = Get-PfxCertificate -FilePath $PfxCertificate -ErrorAction Stop + + $CSNew.PfxCertificate = Join-Path -Path $PfxCertificate + $CSNew.Thumbprint = $Cert.Thumbprint + + + $CredentialItems = $CSOld | Get-Member -MemberType NoteProperty | Where-Object { + $_.Definition -like "*.PSCustomObject*" + } | Select-Object -ExpandProperty Name + + # iterate through all existing items + foreach ($Item in $CredentialItems) { + + $CurrentDate = Get-Date -UFormat "%Y-%m-%d %H:%M:%S" + $RSAKey = Get-RandomAESKey + + $CredentialObj = [PSCustomObject]@{ + User = $Item.UserName + Password = $null + Created = $CurrentDate + LastChange = $null + EncryptedKey = [Convert]::ToBase64String( + $Cert.PublicKey.Key.Encrypt( + $RSAKey, + [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1 + ) + ) + } + if ($CSOld.Type -eq 'Private') { + $CredentialObject.Password = ConvertTo-SecureString -SecureString $Item.Password | ConvertFrom-SecureString -Key $RSAKey + } + elseif ($CSNew.Type -eq 'Shared') { + $ChallengeKey = [io.file]::ReadAllBytes((Join-Path -Path $PWD -ChildPath '/Challenge.bin')) + $CredentialObject.Password = ConvertTo-SecureString -SecureString $Item.Password -Key $ChallengeKey | ConvertFrom-SecureString -Key $RSAKey + } + Add-Member -InputObject $CSNew -Name ( + ($Item | Get-Variable).Name + ) -MemberType NoteProperty -Value $CredentialObj + } + $CSNew | ConvertTo-Json -Depth 5 | Out-File -LiteralPath ( + Join-Path -Path $PWD -ChildPath './CredentialStore.json' + ) -Encoding utf8 -Confirm:$true + } + else { + $ErrorParams = @{ + Message = 'Could not find the given CredentialStore path!' + ErrorAction = 'Stop' + Exception = [System.IO.FileNotFoundException]::new() + } + Write-Error @ErrorParams + } + } + end { + } +} diff --git a/src/Vendor/libressl255/LICENSE b/src/Vendor/libressl255/LICENSE new file mode 100644 index 0000000..f85d4bc --- /dev/null +++ b/src/Vendor/libressl255/LICENSE @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c393690c37b7def75ed9dd1cf01e6b981781203bfe2fef561a6360ca4087d83a +size 6397 diff --git a/src/Vendor/libressl255/libcrypto-41.dll b/src/Vendor/libressl255/libcrypto-41.dll new file mode 100644 index 0000000..37c3f23 --- /dev/null +++ b/src/Vendor/libressl255/libcrypto-41.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58e99e5f73c9722c4c4e23743e533d777bff3337c486d9a945e9aff022125e46 +size 1462912 diff --git a/src/Vendor/libressl255/libcrypto-41.exp b/src/Vendor/libressl255/libcrypto-41.exp new file mode 100644 index 0000000..a70887e --- /dev/null +++ b/src/Vendor/libressl255/libcrypto-41.exp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa14db9365f3e6f30f0bf085e7b33bfd150db6936ec93e481a1d7558436a18a4 +size 454746 diff --git a/src/Vendor/libressl255/libcrypto-41.lib b/src/Vendor/libressl255/libcrypto-41.lib new file mode 100644 index 0000000..b02b3a6 --- /dev/null +++ b/src/Vendor/libressl255/libcrypto-41.lib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c10a4dee83698de9a445b6b5572bc3f2d21d322e223c1812c3726f675e31951f +size 767528 diff --git a/src/Vendor/libressl255/libcrypto-41.pdb b/src/Vendor/libressl255/libcrypto-41.pdb new file mode 100644 index 0000000..b7e1764 --- /dev/null +++ b/src/Vendor/libressl255/libcrypto-41.pdb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3193e446fa1c9d8fed5a1d13a7faeccdb2459121ab1723493b34c91caf56c254 +size 1952768 diff --git a/src/Vendor/libressl255/libssl-43.dll b/src/Vendor/libressl255/libssl-43.dll new file mode 100644 index 0000000..35b13d7 --- /dev/null +++ b/src/Vendor/libressl255/libssl-43.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5785c6c95cf6e5a26389f29cbd1a83702209b0d0cf405ed79eb86966775bd7d8 +size 312957 diff --git a/src/Vendor/libressl255/libssl-43.exp b/src/Vendor/libressl255/libssl-43.exp new file mode 100644 index 0000000..2dacec3 --- /dev/null +++ b/src/Vendor/libressl255/libssl-43.exp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64440d35b269efdce0bda183b02d5384cb86e3c8757d3688d09b07ce85d80121 +size 36738 diff --git a/src/Vendor/libressl255/libssl-43.lib b/src/Vendor/libressl255/libssl-43.lib new file mode 100644 index 0000000..465779b --- /dev/null +++ b/src/Vendor/libressl255/libssl-43.lib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3620a8e6fa138187b9a29b598174c02a9d14c54978c41c871a9ac9791d88b08 +size 61638 diff --git a/src/Vendor/libressl255/libssl-43.pdb b/src/Vendor/libressl255/libssl-43.pdb new file mode 100644 index 0000000..7439a7e --- /dev/null +++ b/src/Vendor/libressl255/libssl-43.pdb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bc96773c21403aa5feae4db58db92d91d93cd3ebc88bc2ff222a1c1ff26c569 +size 388096 diff --git a/src/Vendor/libressl255/libtls-15.dll b/src/Vendor/libressl255/libtls-15.dll new file mode 100644 index 0000000..077e694 --- /dev/null +++ b/src/Vendor/libressl255/libtls-15.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0a5ba84d5eb2d7b3a058fd68be411f936ee855748a5e66938b8810b99d96d70 +size 75901 diff --git a/src/Vendor/libressl255/libtls-15.exp b/src/Vendor/libressl255/libtls-15.exp new file mode 100644 index 0000000..2d1f17f --- /dev/null +++ b/src/Vendor/libressl255/libtls-15.exp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f69c7b2bbe5f268cc2d572dd627d6c23081de6d8152dfff4c13c15b303d84a1 +size 11905 diff --git a/src/Vendor/libressl255/libtls-15.lib b/src/Vendor/libressl255/libtls-15.lib new file mode 100644 index 0000000..f77188e --- /dev/null +++ b/src/Vendor/libressl255/libtls-15.lib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93d30dc5ca2f3f102ee530f306139cca5b392afebc8fa65c054ae17884bb964c +size 20352 diff --git a/src/Vendor/libressl255/libtls-15.pdb b/src/Vendor/libressl255/libtls-15.pdb new file mode 100644 index 0000000..d73e345 --- /dev/null +++ b/src/Vendor/libressl255/libtls-15.pdb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c404a8c410624e8444078eabb913f52e3aadbac6d103f7eb5828f9bea1954219 +size 166912 diff --git a/src/Vendor/libressl255/ocspcheck.exe b/src/Vendor/libressl255/ocspcheck.exe new file mode 100644 index 0000000..417ab92 --- /dev/null +++ b/src/Vendor/libressl255/ocspcheck.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a69805aee7ee6cb71cf057504c4947b75c6fec935166271a05aa323a594960c0 +size 578870 diff --git a/src/Vendor/libressl255/openssl.exe b/src/Vendor/libressl255/openssl.exe new file mode 100644 index 0000000..d63d3bf --- /dev/null +++ b/src/Vendor/libressl255/openssl.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45576fd9f2903fd02cba75d1888d2ced7deab77e8a2e2c3dd05db2d87c81d3a1 +size 2271428 diff --git a/src/openssl.conf b/src/openssl.conf new file mode 100644 index 0000000..43a7404 --- /dev/null +++ b/src/openssl.conf @@ -0,0 +1,245 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days = 30 # how long before next CRL +default_md = md5 # which md to use. +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = DE +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = BW + +localityName = Locality Name (eg, city) +localityName_default = PSCredentialStore + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = PSCredentialStore + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = PSCRedentialStore + +commonName = Common Name (eg, YOUR name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 40 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 0 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints = CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, codeSigning + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, codeSigning + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier = keyid:always,issuer:always diff --git a/tests/00_BasicModule.Tests.ps1 b/tests/00_BasicModule.Tests.ps1 index b605a56..6c846ad 100644 --- a/tests/00_BasicModule.Tests.ps1 +++ b/tests/00_BasicModule.Tests.ps1 @@ -1,15 +1,17 @@ -$RepoRoot = (Get-GitDirectory).replace('\.git', '') +$RepoRoot = (Get-Item -Path (Get-GitDirectory) -Force).Parent | Select-Object -ExpandProperty 'FullName' +Write-Verbose -Message ('RepoRoot: {0}' -f $RepoRoot) -Verbose +$ManifestFilePath = Join-Path -Path $RepoRoot -ChildPath '/src/PSCredentialStore.psd1' +Write-Verbose -Message ("ManifestFilePath: {0}" -f $ManifestFilePath) -Verbose Describe "Pre-Flight module tests" { - $ManifestFilePath = "{0}\src\PSCredentialstore.psd1" -f $RepoRoot Context "Manifest file related" { - It "Test the parsed file itsef" { - { Test-ModuleManifest -Path $ManifestFilePath } | Should -Not -Throw + It "Test the parsed file itself" { + { Test-ModuleManifest -Path $ManifestFilePath -Verbose } | Should -Not -Throw } } Context "Module consistency tests" { - IT "Importing should work" { - { Import-Module -Name $ManifestFilePath -Global -Force }| Should -Not -Throw + It "Importing should work" { + { Import-Module -Name $ManifestFilePath -Global -Force -Verbose } | Should -Not -Throw } } } diff --git a/tests/ChallengeFile/01_Set-ChallengeFile.Tests.ps1 b/tests/ChallengeFile/01_Set-ChallengeFile.Tests.ps1 deleted file mode 100644 index 0a68473..0000000 --- a/tests/ChallengeFile/01_Set-ChallengeFile.Tests.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -#region HEADER -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -. (Get-ChildItem -Path $RepoRoot -Filter "Get-RandomKey.ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Set-ChallengeFile" { - Context "Tests with custom path" { - It "Working dir and path not exist" { - {Set-ChallengeFile -Path 'C:\PSCredentialStore\Challenge.bin'} | Should -Not -Throw - } - It "No parameter and non file should return true" { - if (Test-Path -Path ("{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData)) { - Remove-Item -Path ("{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData) - } - Set-ChallengeFile - Test-Path -Path ("{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData) | Should -Be $true - } - It "Existing Credential file should return error" { - { Set-ChallengeFile } | Should -Throw - Remove-Item -Path ("{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData) - } - It "Use -Force switch should create a new challenge file" { - # prepare for test and clean up old data - if (Test-Path -Path ("{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData)) { - Remove-Item -Path ("{0}\PSCredentialStore\Challenge.bin" -f $env:ProgramData) - } - Set-ChallengeFile - { Set-ChallengeFile -Force } | Should -Not -Throw - } - It "Test directory creation for shared store" { - if (Test-Path -Path ("{0}\PSCredentialStore" -f $env:ProgramData)) { - Remove-Item -Path ("{0}\PSCredentialStore" -f $env:ProgramData) -Force -Recurse - } - Set-ChallengeFile - Test-Path -Path ("{0}\PSCredentialStore" -f $env:ProgramData) | Should -Be $true - } - } - Context "General Exception handling" { - Mock New-Item {throw "foobar exception"} - It "Test exception handling if the root directory could not be created" { - if (Test-Path -Path ("{0}\PSCredentialStore" -f $env:ProgramData)) { - Remove-Item -Path ("{0}\PSCredentialStore" -f $env:ProgramData) -Force -Recurse - } - { Set-ChallengeFile } | Should -Throw "Could not create the parent data dir" - } - } -} diff --git a/tests/ChallengeFile/01_Test-ChallengeFile.Tests.ps1 b/tests/ChallengeFile/01_Test-ChallengeFile.Tests.ps1 deleted file mode 100644 index 285fddc..0000000 --- a/tests/ChallengeFile/01_Test-ChallengeFile.Tests.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -#region HEADER -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -#. (Get-ChildItem -Path $RepoRoot -Filter "Test-ChallengeFile.ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Test-ChallengeFile" { - Context "Basic input tests" { - Mock Test-Path {return $true} - It "No parameter with existing challenge file" { - {Test-ChallengeFile} | Should -Not -Throw - } - It "No parameter and existing file should return true" { - Test-ChallengeFile | Should -Be $true - } - } - Context "Execute with parameter" { - $TestChFile = "{0}\resources\cs\Challenge.bin" -f $RepoRoot - It "Provide valid path" { - Test-ChallengeFile -Path $TestChFile | Should -Be $true - } - It "Provide fake path" { - Test-ChallengeFile -Path "C:\notexisting.bin" | Should -Be $false - } - } -} diff --git a/tests/Helper/01_Get-ModuleBase.Tests.ps1 b/tests/Helper/01_Get-ModuleBase.Tests.ps1 deleted file mode 100644 index 55ba4f6..0000000 --- a/tests/Helper/01_Get-ModuleBase.Tests.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -#region HEADER -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -# . (Get-ChildItem -Path $RepoRoot -Filter ".ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Get-ModuleBase" { - Context "Basic syntax check" { - It "Test1: Should not throw" { - { Get-ModuleBase } | Should -Not -Throw - } - } -} diff --git a/tests/Helper/01_Get-RandomKey.Tests.ps1 b/tests/Helper/01_Get-RandomKey.Tests.ps1 deleted file mode 100644 index f6ab645..0000000 --- a/tests/Helper/01_Get-RandomKey.Tests.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -#region HEADER -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -# . (Get-ChildItem -Path $RepoRoot -Filter ".ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Get-RandomKey" { - Context "Basic input tests" { - It "Test1: Should throw if wrong size is given" { - {Get-RandomKey -size 43} | Should -Throw - } - } - Context "Basic syntax check" { - It "Test1: Should return a key with a length of 16" { - $Key = Get-RandomKey -size 16 - $Key.length | Should -Be 16 - } - It "Test2: Should return a key with a length of 24" { - $Key = Get-RandomKey -size 24 - $Key.length | Should -Be 24 - } - It "Test3: Should return a key with a length of 32" { - $Key = Get-RandomKey -size 32 - $Key.length | Should -Be 32 - } - } -} diff --git a/tests/Helper/01_Test-Module.Tests.ps1 b/tests/Helper/01_Test-Module.Tests.ps1 deleted file mode 100644 index 675a2dc..0000000 --- a/tests/Helper/01_Test-Module.Tests.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -#region HEADER -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -#. (Get-ChildItem -Path $RepoRoot -Filter ".ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Test-ModuleName" { - Context "Basic input tests" { - It "Testing standard module should not throw" { - { Test-Module -Name 'PowerShellGet' -Type Module } | Should -Not -Throw - } - It "Existing module should return true" { - Test-Module -Name 'PowerShellGet' -Type Module | Should -Be $true - } - } - Context "Custom Type tests" { - It "Using custom type should throw" { - { Test-Module -Name "foobarr" -Type Custom} | Should -Throw - } - } - Context "Working with PSSnapins" { - It "Loading first PSSnaping should not throw " { - $Snap = Get-PSSnapin -Registered | Select-Object -First 1 - { Test-Module -Name $Snap.Name -Type PSSnapin } | Should -Not -Throw - } - It "Loading first PSSnaping should return true" { - $Snap = Get-PSSnapin -Registered | Select-Object -First 1 - Test-Module -Name $Snap.Name -Type PSSnapin | Should -Be $true - } - It "Not existing PSSnaping should return false" { - Test-Module -Name 'foobar2000' -Type PSSnapin | Should -Be $false - } - It "StopifFails switch should thrown an error" { - {Test-Module -Name 'foobar2000' -Type PSSnapin -StopIfFails }| Should -Throw - } - } - Context "Working with modules" { - It "Loading first module should not throw " { - $Mod = Get-Module -ListAvailable | Select-Object -First 1 - { Test-Module -Name $Mod.Name -Type Module } | Should -Not -Throw - } - It "Loading first module should return true" { - $Snap = Get-Module -ListAvailable | Select-Object -First 1 - Test-Module -Name $Snap.Name -Type Module | Should -Be $true - } - It "Not existing module should return false" { - Test-Module -Name 'foobar2000' -Type Module | Should -Be $false - } - It "StopifFails switch should thrown an error" { - {Test-Module -Name 'foobar2000' -Type Module -StopIfFails }| Should -Throw - } - } -} diff --git a/tests/Item/02_New-CredentialStoreItem.Tests.ps1 b/tests/Item/02_New-CredentialStoreItem.Tests.ps1 deleted file mode 100644 index 3287508..0000000 --- a/tests/Item/02_New-CredentialStoreItem.Tests.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -#region HEADER -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -# $RepoRoot = (Get-Item -Path $here).Parent.Parent.FullName -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -#. (Get-ChildItem -Path $RepoRoot -Filter "Test-CredentialStore.ps1" -Recurse).FullName -#. (Get-ChildItem -Path $RepoRoot -Filter "New-CredentialStore.ps1" -Recurse).FullName -#. (Get-ChildItem -Path $RepoRoot -Filter "Get-CredentialStore.ps1" -Recurse).FullName -#. (Get-ChildItem -Path $RepoRoot -Filter "Get-CredentialStoreItem.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Test-ChallengeFile.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Get-ChallengeFile.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Set-ChallengeFile.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Get-RandomKey.ps1" -Recurse).FullName - -#endregion HEADER - -Describe "New-CredentialStoreItem" { - Context "Private Credential Store tests" { - It "Test1: Add entry to existing private store." { - If (-not (Test-CredentialStore)) { - New-CredentialStore - } - [String]$tmp = (65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_} - $tmp = $tmp.Replace(' ', '') - $tmpUser = "MyUser" - $tmpPwd = "fooobarysdfsfs" | ConvertTo-SecureString -AsPlainText -Force - $creds = New-Object -TypeName PsCredential -ArgumentList $tmpUser, $tmpPwd - New-CredentialStoreItem -RemoteHost $tmp -Credential $creds - # Had to remove the `{ } | Shoud Not Throw` because the return would be empty. - $content = Get-CredentialStoreItem -RemoteHost $tmp - $content.UserName | Should Be "MyUser" - #Cleanup Temp entry - $CS = Get-CredentialStore - $CS.PSObject.Properties.Remove($tmp) - ConvertTo-Json -InputObject $CS | Out-File -FilePath ("{0}\CredentialStore.json" -f $env:AppData) - } - } - Context "Test with new shared Credential Store" { - It "Test2: Create new RemoteHost entry" { - # prepare test environment - $tmpCS = 'C:\CredentialStore.json' - New-CredentialStore -Shared -Path $tmpCS - - $UserName = "myuser" - $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force - $mycreds = New-Object -TypeName PSCredential -ArgumentList $UserName, $Password - $RemoteHost = "foobar" - { New-CredentialStoreItem -Path $tmpCS -RemoteHost $RemoteHost -Credential $mycreds -Shared } | Should Not Throw - $tmpCS = Get-Content -Path $tmpCS -Raw | ConvertFrom-Json - $res = Get-Member -InputObject $tmpCS -Name $RemoteHost -Membertype Properties - $res.Name | Should Be $RemoteHost - } - It "Adds Item with identifier to shared store" { - $tmpCS = 'C:\CredentialStore.json' - $UserName = "myuser" - $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force - $mycreds = New-Object -TypeName PSCredential -ArgumentList $UserName, $Password - $RemoteHost = "foobar2" - New-CredentialStoreItem -Path $tmpCS -RemoteHost $RemoteHost -Credential $mycreds -Identifier 'Foo' - $writtenItem = Get-CredentialStoreItem -Path $tmpCS -RemoteHost $RemoteHost -Identifier 'Foo' - ($writtenItem.UserName -eq $UserName) -and ($writtenItem.Password.Length -gt 0) | Should -Be $true - } - } - Context "Test optional parameter lookup" { - Mock Get-Credential { - $UserName = 'testuser' - $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force - return [PSCredential]::new($UserName, $Password) - - } - It "Test missing Credential" { - $tmpCS = 'C:\CredentialStore.json' - New-CredentialStoreItem -Path $tmpCs -Shared -RemoteHost 'foobar3' - $writtenItem = Get-CredentialStoreItem -Path $tmpCS -Shared -RemoteHost 'foobar3' - $writtenItem.UserName | Should -Be "testuser" - } - } - Context "General Exception handling" { - Mock Test-CredentialStore {return $false} - Mock Get-Credential { - $UserName = 'myUser' - $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force - return [PSCredential]::new($UserName, $Password) - - } - It "Missing CredentialStore should throw" { - { New-CredentialStoreItem -Path 'C:\missingStore.json' -RemoteHost 'notrelevant' } | Should -Throw "Could not add anything" - } - } - Context "Testing pipeline paramter" { - It "Add the item with credential value from pipe" { - $UserName = 'pipeUser' - $Password = ConvertTo-SecureString -String "pipePasswd" -AsPlainText -Force - { [PSCredential]::new($UserName, $Password) | New-CredentialStoreItem -RemoteHost 'PipeHost' } | Should -Not -Throw - } - - It "Testing written item" { - (Get-CredentialStoreItem -RemoteHost 'PipeHost').UserName | Should -Be 'pipeUser' - } - } - -} diff --git a/tests/Item/03_New-CredentialStoreItem.Tests.ps1 b/tests/Item/03_New-CredentialStoreItem.Tests.ps1 new file mode 100644 index 0000000..8c1dad6 --- /dev/null +++ b/tests/Item/03_New-CredentialStoreItem.Tests.ps1 @@ -0,0 +1,85 @@ +Describe "New-CredentialStoreItem" { + Context "Private Credential Store tests" { + It "Test1: Add entry to existing private store." { + # Creat a fresh CredentialStore first + New-CredentialStore -Force + + [String]$tmp = (65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_} + $tmp = $tmp.Replace(' ', '') + $tmpUser = "MyUser" + $tmpPwd = "fooobarysdfsfs" | ConvertTo-SecureString -AsPlainText -Force + $creds = [PSCredential]::new($tmpUser, $tmpPwd) + New-CredentialStoreItem -RemoteHost $tmp -Credential $creds + # Had to remove the `{ } | Shoud Not Throw` because the return would be empty. + $content = Get-CredentialStoreItem -RemoteHost $tmp + $content.UserName | Should -Be "MyUser" + #Cleanup Temp entry + $CS = Get-CredentialStore + $CS.PSObject.Properties.Remove($tmp) + ConvertTo-Json -InputObject $CS | Out-File -FilePath (Get-DefaultCredentialStorePath) + } + } + Context "Test with new shared Credential Store" { + It "Test2: Create new RemoteHost entry" { + # prepare test environment + $tmpCS = Join-Path -Path (Get-TempDir) -ChildPath '/CredentialStore.json' + New-CredentialStore -Shared -Path $tmpCS -Force + + $UserName = "myuser" + $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force + $mycreds = [PSCredential]::new($UserName, $Password) + $RemoteHost = "foobar" + { New-CredentialStoreItem -Shared -Path $tmpCS -RemoteHost $RemoteHost -Credential $mycreds } | Should -Not -Throw + $tmpCS = Get-Content -Path $tmpCS -Raw | ConvertFrom-Json + $res = Get-Member -InputObject $tmpCS -Name $RemoteHost -Membertype Properties + $res.Name | Should -Be $RemoteHost + } + It "Adds Item with identifier to shared store" { + $tmpCS = Join-Path -Path (Get-TempDir) -ChildPath '/CredentialStore.json' + New-CredentialStore -Shared -Path $tmpCS -Force + + $UserName = "myuser" + $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force + $mycreds = [PSCredential]::new($UserName, $Password) + $RemoteHost = "foobar2" + New-CredentialStoreItem -Shared -Path $tmpCS -RemoteHost $RemoteHost -Credential $mycreds -Identifier 'Foo' + $writtenItem = Get-CredentialStoreItem -Shared -Path $tmpCS -RemoteHost $RemoteHost -Identifier 'Foo' + ($writtenItem.UserName -eq $UserName) -and ($writtenItem.Password.Length -gt 0) | Should -Be $true + } + } + Context "Test optional parameter lookup" { + + It "Test missing Credential" { + function global:Get-Credential ([string]$Message) { + $UserName = 'testuser' + $Password = ConvertTo-SecureString -String "mypasswd" -AsPlainText -Force + return [PSCredential]::new($UserName, $Password) + } + $tmpCS = Join-Path -Path (Get-TempDir) -ChildPath '/CredentialStore.json' + New-CredentialStoreItem -Path $tmpCs -Shared -RemoteHost 'foobar3' + $writtenItem = Get-CredentialStoreItem -Path $tmpCS -Shared -RemoteHost 'foobar3' + $writtenItem.UserName | Should -Be "testuser" + + Remove-Item -Path 'Function:\Get-Credential' + } + + } + Context "General Exception handling" { + Mock Test-CredentialStore {return $false} + It "Missing CredentialStore should throw" { + { New-CredentialStoreItem -Shared -Path 'C:\missingStore.json' -RemoteHost 'notrelevant' } | Should -Throw "Could not add anything" + } + } + Context "Testing pipeline paramter" { + It "Add the item with credential value from pipe" { + $UserName = 'pipeUser' + $Password = ConvertTo-SecureString -String "pipePasswd" -AsPlainText -Force + { [PSCredential]::new($UserName, $Password) | New-CredentialStoreItem -RemoteHost 'PipeHost' } | Should -Not -Throw + } + + It "Testing written item" { + (Get-CredentialStoreItem -RemoteHost 'PipeHost').UserName | Should -Be 'pipeUser' + } + } + +} diff --git a/tests/Private/01_Get-DefaultCredentialStorePath.Tests.ps1 b/tests/Private/01_Get-DefaultCredentialStorePath.Tests.ps1 new file mode 100644 index 0000000..ac4b6dc --- /dev/null +++ b/tests/Private/01_Get-DefaultCredentialStorePath.Tests.ps1 @@ -0,0 +1,49 @@ +Describe "Get-DefaultCredentialStorePath" { + Context "Basic syntax test" { + It "Test1: Should not throw" { + { Get-DefaultCredentialStorePath } | Should -Not -Throw + } + } + + Context "Private Type" { + It "Should return correct paths" { + $Path = Get-DefaultCredentialStorePath + #Write-Verbose -Message ('Delivered path is: {0}' -f $Path) -Verbose + if ($Env:APPVEYOR) { + $PathRef = Join-Path -Path $Env:APPDATA -ChildPath 'CredentialStore.json' + $Path | Should -Be $PathRef + } + elseif ($ENV:TRAVIS) { + if ($IsLinux) { + $PathRef = Join-Path -Path $Env:HOME -ChildPath 'CredentialStore.json' + $Path | Should -Be $PathRef + } + elseif ($IsMacOS) { + $PathRef = Join-Path -Path $Env:HOME -ChildPath 'CredentialStore.json' + $Path | Should -Be $PathRef + } + } + } + } + + Context "Shared Type" { + It "Should return correct paths" { + $Path = Get-DefaultCredentialStorePath -Shared + #Write-Verbose -Message ('Delivered path is: {0}' -f $Path) -Verbose + if ($Env:APPVEYOR) { + $PathRef = Join-Path -Path $env:ProgramData -ChildPath 'PSCredentialStore/CredentialStore.json' + $Path | Should -Be $PathRef + } + elseif ($ENV:TRAVIS) { + if ($IsLinux) { + $PathRef = Join-Path -Path '/var/opt' -ChildPath 'PSCredentialStore/CredentialStore.json' + $Path | Should -Be $PathRef + } + elseif ($IsMacOS) { + $PathRef = Join-Path -Path '/var/opt' -ChildPath 'PSCredentialStore/CredentialStore.json' + $Path | Should -Be $PathRef + } + } + } + } +} diff --git a/tests/Private/01_Get-ModuleBase.Tests.ps1 b/tests/Private/01_Get-ModuleBase.Tests.ps1 new file mode 100644 index 0000000..0e5d972 --- /dev/null +++ b/tests/Private/01_Get-ModuleBase.Tests.ps1 @@ -0,0 +1,7 @@ +Describe "Get-ModuleBase" { + Context "Basic syntax check" { + It "Test1: Should not throw" { + { Get-ModuleBase } | Should -Not -Throw + } + } +} diff --git a/tests/Private/01_Get-RandomAESKey.Tests.ps1 b/tests/Private/01_Get-RandomAESKey.Tests.ps1 new file mode 100644 index 0000000..9a9c72f --- /dev/null +++ b/tests/Private/01_Get-RandomAESKey.Tests.ps1 @@ -0,0 +1,13 @@ +Describe "Get-RandomKey" { + Context "Basic input tests" { + It "Test1: Should not throw " { + {Get-RandomAESKey} | Should -Not -Throw + } + } + Context "Basic syntax check" { + It "Test2: Should return a key with a length of 32 bytes" { + $Key = Get-RandomAESKey + $Key.length | Should -Be 32 + } + } +} diff --git a/tests/Private/01_Get-TempDir.Tests.ps1 b/tests/Private/01_Get-TempDir.Tests.ps1 new file mode 100644 index 0000000..34a3d5e --- /dev/null +++ b/tests/Private/01_Get-TempDir.Tests.ps1 @@ -0,0 +1,27 @@ +Describe "Get-TempDir" { + Context "Basic tests" { + It "Should not throw" { + {Get-TempDir} | Should -Not -Throw + } + It "Should return the correct os tmp path" { + $Path = Get-TempDir + + if ($ENV:TRAVIS) { + if ($IsLinux) { + $RefPath = (Resolve-Path -Path '/tmp/').Path + $Path | Should -Be $RefPath + } + if ($IsMacOS) { + $RefPath = (Resolve-Path -Path '/tmp/').Path + $Path | Should -Be $RefPath + } + } + if ($Env:APPVEYOR) { + if (($isWindows) -or ($PSVersionTable.PSVersion.Major -lt 6) -or ($PSVersionTable.PSEdition -eq 'Desktop')) { + $RefPath = (Resolve-Path -Path $env:TEMP).Path + $Path | Should -Be $RefPath + } + } + } + } +} diff --git a/tests/Helper/01_Resolve-Dependency.Tests.ps1 b/tests/Private/01_Resolve-Dependency.Tests.ps1 similarity index 50% rename from tests/Helper/01_Resolve-Dependency.Tests.ps1 rename to tests/Private/01_Resolve-Dependency.Tests.ps1 index 4d8f30e..3a4a19c 100644 --- a/tests/Helper/01_Resolve-Dependency.Tests.ps1 +++ b/tests/Private/01_Resolve-Dependency.Tests.ps1 @@ -1,27 +1,6 @@ -#region HEADER -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -# $RepoRoot = (Get-Item -Path $here).Parent.Parent.FullName -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -. (Get-ChildItem -Path $RepoRoot -Filter "Get-ModuleBase.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Test-Module.ps1" -Recurse).FullName - -#endregion HEADER - Describe "Resolve-Dependency" { Context "Basic syntax check" { - Mock Get-ModuleBase {return "{0}\resources" -f $PWD} + Mock Get-ModuleBase {return (Join-Path -Path $PWD -ChildPath '/resources')} Mock Test-Module {return $true} It "Test1: Should not throw" { { Resolve-Dependency -Name 'foobar2000' } | Should -Not -Throw @@ -32,7 +11,10 @@ Describe "Resolve-Dependency" { } Context "Enforce Error" { # Return incorrect module base to enforce there is no config file. - Mock Get-ModuleBase {return "C:\"} + Mock Get-ModuleBase { + if ($IsWindows) {return "C:\"} + elseif ($isLinux) {return "/"} + } It "Missing dependency file should not cause an error" { { Resolve-Dependency -Name 'awesome'} | Should -Not -Throw } @@ -42,7 +24,7 @@ Describe "Resolve-Dependency" { } } Context "Testing input variations" { - Mock Get-ModuleBase {return "{0}\resources" -f $PWD} + Mock Get-ModuleBase {return (Join-Path -Path $PWD -ChildPath '/resources')} It "Should return true if all given dependencies exist" { Resolve-Dependency -Name 'Existing' | Should -Be $true } diff --git a/tests/Private/01_Test-Module.Tests.ps1 b/tests/Private/01_Test-Module.Tests.ps1 new file mode 100644 index 0000000..c5747c8 --- /dev/null +++ b/tests/Private/01_Test-Module.Tests.ps1 @@ -0,0 +1,26 @@ +Describe "Test-ModuleName" { + Context "Basic input tests" { + It "Testing standard module should not throw" { + { Test-Module -Name 'PowerShellGet' } | Should -Not -Throw + } + It "Existing module should return true" { + Test-Module -Name 'PowerShellGet' | Should -Be $true + } + } + Context "Working with modules" { + It "Loading first module should not throw " { + $Mod = Get-Module -ListAvailable | Select-Object -First 1 + { Test-Module -Name $Mod.Name } | Should -Not -Throw + } + It "Loading first module should return true" { + $Snap = Get-Module -ListAvailable | Select-Object -First 1 + Test-Module -Name $Snap.Name | Should -Be $true + } + It "Not existing module should return false" { + Test-Module -Name 'foobar2000' | Should -Be $false + } + It "StopifFails switch should thrown an error" { + {Test-Module -Name 'foobar2000' -StopIfFails }| Should -Throw + } + } +} diff --git a/tests/Store/01_Get-CredentialStore.Tests.ps1 b/tests/Store/01_Get-CredentialStore.Tests.ps1 deleted file mode 100644 index e52f147..0000000 --- a/tests/Store/01_Get-CredentialStore.Tests.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -#region HEADER -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -# $RepoRoot = (Get-Item -Path $here).Parent.Parent.FullName -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -#. (Get-ChildItem -Path $RepoRoot -Filter "Test-CredentialStore.ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Get-CredentialStore" { - Context "Basic logic tests" { - $TestCredentialStore = Resolve-Path -Path ("{0}\resources\cs\CredentialStore.json" -f $RepoRoot) - It "Test1: Read CS without params" { - If (Test-Path -Path ("{0}\CredentialStore.json" -f $env:APPDATA)) { - {Get-CredentialStore} | Should Not Throw - } - Else { - Write-Warning "Default private Credential Store not found. Skipping..." - } - } - It "Test2: Read Credential Store with testing data" { - - {Get-CredentialStore -Path $TestCredentialStore} | Should Not Throw - } - It "Test3: Not existing path should return false" { - { Get-CredentialStore -Path 'C:\foobar\CredentialStore.json' -Shared }| Should -Throw "Could not find the CredentialStore." - } - } - Context "Testing invalid json data" { - Mock Test-CredentialStore {return $true} - Mock Get-Content {return '"foo":"bar",'} - It "Should throw with invalid CredentialStore" { - { Get-Credentialstore -Path "C:\dummy.json"} | Should -Throw "Unknown CredentialStore format. Invalid JSON file." - } - } -} diff --git a/tests/Store/01_New-CredentialStore.Tests.ps1 b/tests/Store/01_New-CredentialStore.Tests.ps1 deleted file mode 100644 index b9d2eee..0000000 --- a/tests/Store/01_New-CredentialStore.Tests.ps1 +++ /dev/null @@ -1,120 +0,0 @@ -#region HEADER -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -# $RepoRoot = (Get-Item -Path $here).Parent.Parent.FullName -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -#. (Get-ChildItem -Path $RepoRoot -Filter "Test-CredentialStore.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Test-ChallengeFile.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Set-ChallengeFile.ps1" -Recurse).FullName -. (Get-ChildItem -Path $RepoRoot -Filter "Get-RandomKey.ps1" -Recurse).FullName - -#endregion HEADER - - -# Backup existing credential stores -$VerbosePreference = "Continue" -Write-Verbose "Backup private Credential Store..." -$CSPath = ("{0}\CredentialStore.json" -f $env:APPDATA) -$BackupFile = "{0}.back" -f $CSPath -If (Test-Path -Path $CSPath) { - Move-Item -Path $CSPath -Destination $BackupFile -} -Write-Verbose "Backup shared CredentialStore..." -$CSShared = ("{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData) -$BackupSharedFile = "{0}.back" -f $CSShared -If (Test-Path -Path $CSShared) { - Move-Item -Path $CSShared -Destination $BackupSharedFile -} -Write-Verbose "Remove old CredentialStore in Temp dir" -$CSTemp = "{0}\CredentialStore.json" -f $Env:TEMP -If (Test-Path -Path $CSTemp) { - Remove-Item -Path $CSTemp -} -$VerbosePreference = "SilentlyContinue" - -Describe "New-CredentialStore" { - Context "Private CS tests" { - $pCS = Join-Path -Path $env:APPDATA -ChildPath "CredentialStore.json" - It "Test1: Create new private CredentialStore" { - New-CredentialStore - $result = Test-Path -Path $pCS - $CS = Get-Content -Path $pCS -Raw | ConvertFrom-Json - ($result -eq $True) -and ($CS.Type -eq "Private") | Should Be $True - } - It "Test2: Try to override private Store" { - {New-CredentialStore} | Should Throw - } - It "Test3: Reset existing Credential Store" { - $now = Get-Date - $CS = Get-Content -Path $pCS -Raw | ConvertFrom-Json - $CSCreation = [DateTime]$CS.Creation - New-CredentialStore -Force - $now -gt $csCreation | Should Be $True - } - } - Context "Shared CS tests" { - $pCS = Join-Path -Path $env:ProgramData -ChildPath "PSCredentialStore\CredentialStore.json" - It "Test1: Create a new Shared Credential Store" { - New-CredentialStore -Shared - Test-Path -Path ("{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData) | Should Be $True - } - It "Test2: Try to override existing shared CS" { - {New-CredentialStore -Shared} | Should Throw - } - It "Test3: Reset shared CredentialStore" { - $now = Get-Date - $CS = Get-Content -Path $pCS -Raw | ConvertFrom-Json - $CSCreation = [DateTime]$CS.Creation - New-CredentialStore -Force -Shared - $now -gt $csCreation | Should Be $True - } - } - Context "Custom Shared CS tests" { - $pCS = Join-Path -Path $env:TEMP -ChildPath "CredentialStore.json" - It "Test1: Create new custom shared" { - {New-CredentialStore -Path $pCS -Shared} | Should Not Throw - } - It "Test2: Try to override exiting one" { - {New-CredentialStore -Path $pCS -Shared} | Should Throw - } - It "Test3: Reset existing custom CredentialStore" { - {New-CredentialStore -Path $pCS -Shared -Force} | Should Not Throw - } - } - Context "Test exception handling" { - Mock Out-File {throw "foobar exception"} - It "JSON Converstion should fail and throw" { - { New-CredentialStore -Path "C:\dummy.json"} | Should -Throw - } - } -} - -# Cleanup test stores and restore existing ones. -$VerbosePreference = "Continue" -Write-Verbose "Restoring private CredentialStore" -If (Test-Path -Path $BackupFile) { - If (Test-Path -Path $CSPath) { - Remove-Item -Path $CSPath - Move-Item -Path $BackupFile -Destination $CSPath - } -} - -Write-Verbose "Restoring shared CredentialStore" -If (Test-Path -Path $BackupSharedFile) { - If (Test-Path -Path $CSShared) { - Remove-Item -Path $CSShared - Move-Item -Path $BackupSharedFile -Destination $CSShared - } -} -$VerbosePreference = "SilentlyContinue" diff --git a/tests/Store/01_Test-CredentialStore.Tests.ps1 b/tests/Store/01_Test-CredentialStore.Tests.ps1 deleted file mode 100644 index b32738f..0000000 --- a/tests/Store/01_Test-CredentialStore.Tests.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -#region HEADER -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -# $RepoRoot = (Get-Item -Path $here).Parent.Parent.FullName -$RepoRoot = (Get-GitDirectory).replace('\.git', '') -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -$sut = $sut -replace "\d{2}`_", '' -$suthome = (Get-ChildItem -Path $RepoRoot -Exclude ".\tests\" -Filter $sut -Recurse).FullName -# Skip try loading the source file if it doesn't exists. -If ($suthome.Length -gt 0) { - . $suthome -} -Else { - Write-Warning ("Could not find source file {0}" -f $sut) -} - -# load additional functions defined in the repository. Replace the expression . -# . (Get-ChildItem -Path $RepoRoot -Filter ".ps1" -Recurse).FullName - -#endregion HEADER - -Describe "Test-CredentialStore" { - Context "Basic logic tests" { - $TestCredentialStore = Resolve-Path -Path ("{0}\resources\cs\CredentialStore.json" -f $RepoRoot) - It "Test1: Should Not Throw" { - { Test-CredentialStore -Path $TestCredentialStore } | Should Not Throw - } - It "Test2: Read valid CredentialStore" { - $res = Test-CredentialStore -Path $TestCredentialStore - $res | Should Be $True - } - It "Test3: Read a broken CredentialStore" { - $BrokenCS = Resolve-Path -Path ("{0}\resources\cs\Broken_CS.json" -f $RepoRoot) - $oWarningPreference = $WarningPreference - $WarningPreference = 'SilentlyContinue' - $res = Test-CredentialStore -Path $BrokenCS - $res | Should Be $False - $WarningPreference = $oWarningPreference - } - It "Test4: Not existing path should return false" { - Test-CredentialStore -Path 'C:\foobar\CredentialStore.json' | Should -Be $false - } - It "Test5: testing private CredentialStore path" { - if (Test-Path -Path ("{0}\CredentialStore.json" -f $env:APPDATA) ) { - Remove-Item -Path ("{0}\CredentialStore.json" -f $env:APPDATA) - } - Test-CredentialStore | Should -Be $false - } - } -} diff --git a/tests/Store/02_New-CredentialStore.Tests.ps1 b/tests/Store/02_New-CredentialStore.Tests.ps1 new file mode 100644 index 0000000..f822940 --- /dev/null +++ b/tests/Store/02_New-CredentialStore.Tests.ps1 @@ -0,0 +1,96 @@ +# Backup existing credential stores +$VerbosePreference = "Continue" +Write-Verbose "Backup private Credential Store..." +$CSPath = Get-DefaultCredentialStorePath +$BackupFile = "{0}.back" -f $CSPath +If (Test-Path -Path $CSPath) { + Move-Item -Path $CSPath -Destination $BackupFile +} +Write-Verbose "Backup shared CredentialStore..." +$CSShared = Get-DefaultCredentialStorePath -Shared +$BackupSharedFile = "{0}.back" -f $CSShared +If (Test-Path -Path $CSShared) { + Move-Item -Path $CSShared -Destination $BackupSharedFile +} +Write-Verbose "Remove old CredentialStore in Temp dir" +$CSTemp = Join-Path -Path (Get-TempDir) -ChildPath '/CredentialStore.json' +If (Test-Path -Path $CSTemp) { + Remove-Item -Path $CSTemp +} +$VerbosePreference = "SilentlyContinue" + +Describe "New-CredentialStore" { + Context "Private CS tests" { + $pCS = Get-DefaultCredentialStorePath + It "Test1: Create new private CredentialStore" { + { New-CredentialStore -Confirm:$false } | Should -Not -Throw + $result = Test-Path -Path $pCS + $CS = Get-Content -Path $pCS -Raw | ConvertFrom-Json + ($result -eq $true) -and ($CS.Type -eq "Private") | Should -Be $true + } + It "Test2: Try to override private Store" { + { New-CredentialStore -Confirm:$false } | Should -Throw + } + It "Test3: Reset existing Credential Store" { + $now = Get-Date + $CS = Get-Content -Path $pCS -Raw | ConvertFrom-Json + $CSCreation = [DateTime]$CS.Created + New-CredentialStore -Confirm:$false -Force + $now -gt $csCreation | Should -Be $true + } + } + Context "Shared CS tests" { + $sCS = Get-DefaultCredentialStorePath -Shared + It "Test1: Create a new Shared Credential Store" { + { New-CredentialStore -Confirm:$false -Shared } | Should -Not -Throw + Test-Path -Path $sCS | Should -Be $true + } + It "Test2: Try to override existing shared CS" { + {New-CredentialStore -Shared -Confirm:$false} | Should -Throw + } + It "Test3: Reset shared CredentialStore" { + $now = Get-Date + $CS = Get-Content -Path $sCS -Raw | ConvertFrom-Json + $CSCreation = [DateTime]$CS.Created + New-CredentialStore -Force -Shared -Confirm:$false + $now -gt $csCreation | Should -Be $true + } + } + Context "Custom Shared CS tests" { + $cCS = Join-Path -Path (Get-TempDir) -ChildPath "CredentialStore.json" + It "Test1: Create new custom shared" { + {New-CredentialStore -Path $cCS -Shared -Confirm:$false} | Should -Not -Throw + } + It "Test2: Try to override exiting one" { + {New-CredentialStore -Path $cCS -Shared -Confirm:$false} | Should -Throw + } + It "Test3: Reset existing custom CredentialStore" { + {New-CredentialStore -Path $cCS -Shared -Force -Confirm:$false} | Should -Not -Throw + } + } + Context "Test exception handling" { + Mock Out-File {throw "foobar exception"} + It "JSON Conversion should fail and throw" { + { New-CredentialStore -Path (Join-Path -Path (Get-TempDir) -ChildPath '/dummy.json') -Shared -Confirm:$false} | Should -Throw + } + } +} + +# Cleanup test stores and restore existing ones. +$VerbosePreference = "Continue" +Write-Verbose "Restoring private CredentialStore" +If (Test-Path -Path $BackupFile) { + If (Test-Path -Path $CSPath) { + Remove-Item -Path $CSPath + Move-Item -Path $BackupFile -Destination $CSPath + } +} + +Write-Verbose "Restoring shared CredentialStore" +If (Test-Path -Path $BackupSharedFile) { + If (Test-Path -Path $CSShared) { + Remove-Item -Path $CSShared + Move-Item -Path $BackupSharedFile -Destination $CSShared + } +} +$VerbosePreference = "SilentlyContinue" diff --git a/tests/Store/02_Test-CredentialStore.Tests.ps1 b/tests/Store/02_Test-CredentialStore.Tests.ps1 new file mode 100644 index 0000000..6cdeca9 --- /dev/null +++ b/tests/Store/02_Test-CredentialStore.Tests.ps1 @@ -0,0 +1,36 @@ +$RepoRoot = (Get-Item -Path (Get-GitDirectory) -Force).Parent | Select-Object -ExpandProperty 'FullName' + +Describe "Test-CredentialStore" { + Context "Basic logic tests" { + $TestCredentialStore = Join-Path -Path $RepoRoot -ChildPath '/resources/cs/CredentialStore.json' + It "Test1: Should Not Throw" { + { Test-CredentialStore -Shared -Path $TestCredentialStore } | Should -Not -Throw + } + It "Test2: Read valid CredentialStore" { + $res = Test-CredentialStore -Shared -Path $TestCredentialStore + $res | Should -Be $true + } + It "Test3: Read a broken CredentialStore" { + $BrokenCS = Join-Path -Path $RepoRoot -ChildPath '{0}/resources/cs/Broken_CS.json' + $oWarningPreference = $WarningPreference + $WarningPreference = 'SilentlyContinue' + $res = Test-CredentialStore -Shared -Path $BrokenCS + $res | Should -Be $false + $WarningPreference = $oWarningPreference + } + It "Test4: Not existing path should return false" { + if ($isWindows -or ($PSVersionTable.PSVersion.Major -eq 5)) { + Test-CredentialStore -Shared -Path 'C:\foobar\CredentialStore.json' | Should -Be $false + } + elseif ($isWindows -or $IsMacOS) { + Test-CredentialStore -Shared -Path '/var/opt/foo.json' | Should -Be $false + } + } + It "Test5: testing private CredentialStore path" { + if (Test-Path -Path (Get-DefaultCredentialStorePath)) { + Remove-Item -Path (Get-DefaultCredentialStorePath) + } + Test-CredentialStore | Should -Be $false + } + } +} diff --git a/tests/Store/03_Get-CredentialStore.Tests.ps1 b/tests/Store/03_Get-CredentialStore.Tests.ps1 new file mode 100644 index 0000000..a8eabeb --- /dev/null +++ b/tests/Store/03_Get-CredentialStore.Tests.ps1 @@ -0,0 +1,32 @@ +$RepoRoot = (Get-Item -Path (Get-GitDirectory) -Force).Parent | Select-Object -ExpandProperty 'FullName' + +Describe "Get-CredentialStore" { + Context "Basic logic tests" { + $TestCredentialStore = Join-Path -Path $RepoRoot -ChildPath 'resources/cs/CredentialStore.json' + $TestPfxCert = Join-Path -Path $RepoRoot -ChildPath 'resources/cs/PSCredentialStore.pfx' + 'TestCredentialStore: {0}' -f $TestCredentialStore + It "Test1: Read CS without params" { + if (! (Test-Path -Path (Get-DefaultCredentialStorePath)) ) { + { New-CredentialStore -Force } | Should -Not -Throw + + } + { Get-CredentialStore } | Should -Not -Throw + } + It "Test2: Read Credential Store with testing data" { + { Use-PfxCertificate -Shared -CredentialStore $TestCredentialStore -Path $TestPfxCert } | Should -Not -Throw + { Get-CredentialStore -Shared -Path $TestCredentialStore } | Should -Not -Throw + } + It "Test3: Not existing path should return false" { + { Get-CredentialStore -Shared -Path './CredentialStore.json' }| Should -Throw "Could not find the CredentialStore." + } + } + Context "Testing invalid json data" { + #Mock Test-CredentialStore {return $true} + #Mock Get-Content {return '"foo":"bar",'} + $BrokenCS = Join-Path -Path $RepoRoot -ChildPath 'resources/cs/Broken_CS.json' + Write-Verbose -Message ('BrokenCS Path: {0}' -f $BrokenCS) -Verbose + It "Should throw with invalid CredentialStore" { + { Get-CredentialStore -Path -Shared $BrokenCS } | Should -Throw + } + } +} diff --git a/tools/AppVeyor.psm1 b/tools/AppVeyor.psm1 index ac35426..833811d 100644 --- a/tools/AppVeyor.psm1 +++ b/tools/AppVeyor.psm1 @@ -7,7 +7,7 @@ - Module name #> $CALLSIGN = 'PSCredentialStore' -Write-Host ("Callsign is: {0}" -f $CALLSIGN) -ForegroundColor Yellow +Write-Host ("Callsign is: {0}" -f $CALLSIGN) -ForegroundColor Black -BackgroundColor Yellow Function Invoke-InstallDependencies() { @@ -16,18 +16,19 @@ Function Invoke-InstallDependencies() { Process { Try { + Get-PackageProvider -ListAvailable Install-PackageProvider -Name NuGet -RequiredVersion '2.8.5.208' -Force -Verbose Import-PackageProvider -Name NuGet -RequiredVersion '2.8.5.208' -Force - Install-Module -Name 'Pester' -Scope CurrentUser -RequiredVersion '4.0.8' -Force -SkipPublisherCheck -AllowClobber - Install-Module -Name 'posh-git' -Scope CurrentUser -RequiredVersion '0.7.1' -Force -SkipPublisherCheck -AllowClobber - Install-Module -Name 'PSCoverage' -Scope CurrentUser -Force -SkipPublisherCheck -AllowClobber - Import-Module -Name 'Pester', 'posh-git', 'PSCoverage' + Install-Module -Name 'Pester' -Scope CurrentUser -RequiredVersion '4.4.2' -Force -SkipPublisherCheck -AllowClobber + Install-Module -Name 'posh-git' -Scope CurrentUser -RequiredVersion '1.0.0-beta2' -Force -SkipPublisherCheck -AllowClobber -AllowPrerelease + # Install-Module -Name 'PSCoverage' -Scope CurrentUser -Force -SkipPublisherCheck -AllowClobber + Import-Module -Name 'Pester', 'posh-git' #, 'PSCoverage' } Catch { $MsgParams = @{ - Message = 'Could not install the required dependencies!' + Message = 'Could not install the required dependencies!' Category = 'Error' - Details = $_.Exception.Message + Details = $_.Exception.Message } Add-AppveyorMessage @MsgParams Throw $MsgParams.Message @@ -39,21 +40,21 @@ Function Invoke-AppVeyorBumpVersion() { [CmdletBinding()] Param() - Write-Host "Listing Env Vars for debugging:" -ForegroundColor Yellow + Write-Host "Listing Env Vars for debugging:" -ForegroundColor Black -BackgroundColor Yellow # Filter Results to prevent exposing secure vars. Get-ChildItem -Path "Env:*" | Where-Object { $_.name -notmatch "(NuGetToken|CoverallsToken)"} | Sort-Object -Property Name | Format-Table Try { $ModManifest = Get-Content -Path (".\src\{0}.psd1" -f $CALLSIGN) - $BumpedManifest = $ModManifest -replace '0.0.0.9999', $Env:APPVEYOR_BUILD_VERSION + $BumpedManifest = $ModManifest -replace '0.0.9999', $Env:APPVEYOR_BUILD_VERSION Remove-Item -Path (".\src\{0}.psd1" -f $CALLSIGN) Out-File -FilePath (".\src\{0}.psd1" -f $CALLSIGN) -InputObject $BumpedManifest -NoClobber -Encoding utf8 -Force } Catch { $MsgParams = @{ - Message = 'Could not bump current version into module manifest.' + Message = 'Could not bump current version into module manifest.' Category = 'Error' - Details = $_.Exception.Message + Details = $_.Exception.Message } Add-AppveyorMessage @MsgParams Throw $MsgParams.Message @@ -62,27 +63,29 @@ Function Invoke-AppVeyorBumpVersion() { Function Invoke-AppVeyorBuild() { [CmdletBinding()] + [OutputType([PsCustomObject])] Param() $MsgParams = @{ - Message = 'Creating build artifacts' + Message = 'Creating build artifacts' Category = 'Information' - Details = 'Extracting source files and compressing them into zip file.' + Details = 'Extracting source files and compressing them into zip file.' } Add-AppveyorMessage @MsgParams $CompParams = @{ - Path = "{0}\src\*" -f $env:APPVEYOR_BUILD_FOLDER + Path = "{0}\src\*" -f $env:APPVEYOR_BUILD_FOLDER DestinationPath = "{0}\bin\{1}.zip" -f $env:APPVEYOR_BUILD_FOLDER, $CALLSIGN - Update = $True - Verbose = $True + Update = $True + Verbose = $True } Compress-Archive @CompParams $MsgParams = @{ - Message = 'Pushing artifacts' + Message = 'Pushing artifacts' Category = 'Information' - Details = 'Pushing artifacts to AppVeyor store.' + Details = 'Pushing artifacts to AppVeyor store.' } Add-AppveyorMessage @MsgParams - Push-AppveyorArtifact (".\bin\{0}.zip" -f $CALLSIGN) + $ArtifactPath = Join-Path -Path '.' -ChildPath ('bin/{0}.zip' -f $CALLSIGN) + Push-AppveyorArtifact $ArtifactPath } Function Invoke-AppVeyorTests() { @@ -90,60 +93,85 @@ Function Invoke-AppVeyorTests() { Param() $MsgParams = @{ - Message = 'Starting Pester tests' + Message = 'Starting Pester tests' Category = 'Information' - Details = 'Now running all test found in .\tests\ dir.' + Details = 'Now running all test found in .\tests\ dir.' } Add-AppveyorMessage @MsgParams - $testresults = Invoke-Pester -Path ( Get-ChildItem -Path ".\tests\*.Tests.ps1" -Recurse | Sort-Object -Property Name ) -ExcludeTag 'Disabled' -PassThru - ForEach ($Item in $testresults.TestResult) { + + try { + Write-Host '===== Preload internal private functions =====' -ForegroundColor Black -BackgroundColor Yellow + + $Privates = Get-ChildItem -Path (Join-Path -Path $Env:APPVEYOR_BUILD_FOLDER -ChildPath '/src/Private/*') -Include "*.ps1" -Recurse + foreach ($File in $Privates) { + if (Test-Path -Path $File.FullName) { + . $File.FullName + Write-Verbose -Message ('Private function dot-sourced: {0}' -f $File.FullName) -Verbose + } + else { + Write-Warning -Message ('Could not find file: {0} !' -f $File.FullName) + } + } + } + catch { + $_.Exception.Message | Write-Error + throw 'Could not load required private functions!' + } + + #$testresults = Invoke-Pester -Path ( Get-ChildItem -Path ".\tests\*.Tests.ps1" -Recurse | Sort-Object -Property Name ) -ExcludeTag 'Disabled' -PassThru + $srcFiles = Get-ChildItem -Path ".\src\*.ps1" -Recurse | Sort-Object -Property 'Name' | Select-Object -ExpandProperty 'FullName' + $testFiles = Get-ChildItem -Path ".\tests\*.Tests.ps1" -Recurse | Sort-Object -Property 'Name' | Select-Object -ExpandProperty 'FullName' + $TestResults = Invoke-Pester -Path $testFiles -CodeCoverage $srcFiles -PassThru + ForEach ($Item in $TestResults.TestResult) { Switch ($Item.Result) { "Passed" { $TestParams = @{ - Name = "{0}: {1}" -f $Item.Context, $Item.Name + Name = "{0}: {1}" -f $Item.Context, $Item.Name Framework = "NUnit" - Filename = $Item.Describe - Outcome = "Passed" - Duration = $Item.Time.Milliseconds + Filename = $Item.Describe + Outcome = "Passed" + Duration = $Item.Time.Milliseconds } Add-AppveyorTest @TestParams } "Failed" { $TestParams = @{ - Name = "{0}: {1}" -f $Item.Context, $Item.Name - Framework = "NUnit" - Filename = $Item.Describe - Outcome = "Failed" - Duration = $Item.Time.Milliseconds - ErrorMessage = $Item.FailureMessage + Name = "{0}: {1}" -f $Item.Context, $Item.Name + Framework = "NUnit" + Filename = $Item.Describe + Outcome = "Failed" + Duration = $Item.Time.Milliseconds + ErrorMessage = $Item.FailureMessage ErrorStackTrace = $Item.StackTrace } Add-AppveyorTest @TestParams } Default { $TestParams = @{ - Name = "{0}: {1}" -f $Item.Context, $Item.Name - Framework = "NUnit" - Filename = $Item.Describe - Outcome = "None" - Duration = $Item.Time.Milliseconds - ErrorMessage = $Item.FailureMessage + Name = "{0}: {1}" -f $Item.Context, $Item.Name + Framework = "NUnit" + Filename = $Item.Describe + Outcome = "None" + Duration = $Item.Time.Milliseconds + ErrorMessage = $Item.FailureMessage ErrorStackTrace = $Item.StackTrace } Add-AppveyorTest @TestParams } } } - If ($testresults.FailedCount -gt 0) { + If ($TestResults.FailedCount -gt 0) { $MsgParams = @{ - Message = 'Pester Tests failed.' + Message = 'Pester Tests failed.' Category = 'Error' - Details = "$($testresults.FailedCount) tests failed." + Details = "$($TestResults.FailedCount) tests failed." } Add-AppveyorMessage @MsgParams Throw $MsgParams.Message } + return $TestResults.CodeCoverage + } Function Invoke-CoverageReport() { @@ -151,13 +179,17 @@ Function Invoke-CoverageReport() { Param( [Parameter(Mandatory = $False)] [ValidateNotNullOrEmpty()] - [String]$RepoToken = $Env:CoverallsToken + [String]$RepoToken = $Env:CoverallsToken, + + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [PSCustomObject]$PesterCoverageReport ) - $FileMap = New-PesterFileMap -SourceRoot '.\src' -PesterRoot '.\tests' - $CoverageReport = New-CoverageReport -PesterFileMap $FileMap -RepoToken $RepoToken + #$CoverageReport | Format-Custom -Depth 5 | Out-String | Write-Verbose + $CoverageReport = New-CoverageReport -CodeCoverage $PesterCoverageReport -RepoToken $RepoToken Write-Host "CoverageReport JSON:" -ForegroundColor Yellow - $CoverageReport | Out-String | Write-Host + #$CoverageReport | ConvertTo-Json -Depth 5 | Out-String | Write-Verbose Publish-CoverageReport -CoverageReport $CoverageReport } @@ -170,36 +202,36 @@ Function Invoke-AppVeyorPSGallery() { ) Expand-Archive -Path (".\bin\{0}.zip" -f $CALLSIGN) -DestinationPath ("C:\Users\appveyor\Documents\WindowsPowerShell\Modules\{0}\" -f $CALLSIGN) -Verbose Import-Module -Name $CALLSIGN -Verbose -Force - Write-Host "Available Package Provider:" -ForegroundColor Yellow + Write-Host "Available Package Provider:" -ForegroundColor Black -BackgroundColor Yellow Get-PackageProvider -ListAvailable - Write-Host "Available Package Sources:" -ForegroundColor Yellow + Write-Host "Available Package Sources:" -ForegroundColor Black -BackgroundColor Yellow Get-PackageSource Try { - Write-Host "Try to get NuGet Provider:" -ForegroundColor Yellow + Write-Host "Try to get NuGet Provider:" -ForegroundColor Black -BackgroundColor Yellow Get-PackageProvider -Name NuGet -ErrorAction Stop } Catch { - Write-Host "Installing NuGet..." -ForegroundColor Yellow + Write-Host "Installing NuGet..." -ForegroundColor Black -BackgroundColor Yellow Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Force -Verbose Import-PackageProvider NuGet -MinimumVersion '2.8.5.201' -Force } Try { If ($env:APPVEYOR_REPO_BRANCH -eq 'master') { - Write-Host "try to publish module" -ForegroundColor Yellow - Write-Host ("Callsign is: {0}" -f $CALLSIGN) -ForegroundColor Yellow - Publish-Module -Name $CALLSIGN -NuGetApiKey $env:NuGetToken -Verbose -Force + Write-Host "try to publish module" -ForegroundColor Black -BackgroundColor Yellow + Write-Host ("Callsign is: {0}" -f $CALLSIGN) -ForegroundColor Black -BackgroundColor Yellow + Publish-Module -Name $CALLSIGN -NuGetApiKey $env:NuGetToken -Verbose -Force -AllowPrerelease } Else { - Write-Host "Skip publishing to PS Gallery because we are on $($env:APPVEYOR_REPO_BRANCH) branch." -ForegroundColor Yellow + Write-Host "Skip publishing to PS Gallery because we are on $($env:APPVEYOR_REPO_BRANCH) branch." -ForegroundColor Black -BackgroundColor Yellow # had to remove the publish-Module statement because it would publish although the -WhatIf is given. # Publish-Module -Name $CALLSIGN -NuGetApiKey $env:NuGetToken -Verbose -WhatIf } } Catch { $MsgParams = @{ - Message = 'Could not deploy module to PSGallery.' + Message = 'Could not deploy module to PSGallery.' Category = 'Error' - Details = $_.Exception.Message + Details = $_.Exception.Message } Add-AppveyorMessage @MsgParams Throw $MsgParams.Message diff --git a/tools/CoverallsIO.psm1 b/tools/CoverallsIO.psm1 new file mode 100644 index 0000000..1e9b8c8 --- /dev/null +++ b/tools/CoverallsIO.psm1 @@ -0,0 +1,178 @@ +function Get-GitInfo { + [CmdletBinding()] + param( + [string]$BranchName + ) + + if ($Env:AppVeyor) { + return [PSCustomObject]@{ + head = [PSCustomObject]@{ + id = $Env:APPVEYOR_REPO_COMMIT + author_name = $Env:APPVEYOR_REPO_COMMIT_AUTHOR + author_email = $Env:APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL + comitter_name = $Env:APPVEYOR_REPO_COMMIT_AUTHOR + comitter_email = $Env:APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL + message = $Env:APPVEYOR_REPO_COMMIT_MESSAGE + } + branch = $Env:APPVEYOR_REPO_BRANCH + } + } + else { + if (-not $BranchName) { + $BranchName = (git rev-parse --abbrev-ref HEAD) + } + return [PSCustomObject]@{ + head = [PSCustomObject]@{ + id = (git log --format="%H" HEAD -1) + author_name = (git log --format="%an" HEAD -1) + author_email = (git log --format="%ae" HEAD -1) + committer_name = (git log --format="%cn" HEAD -1) + committer_email = (git log --format="%ce" HEAD -1) + message = (git log --format="%s" HEAD -1) + } + branch = $BranchName + } + } +} + +function New-CoverageReport { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [PSCustomObject]$CodeCoverage, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$RepoToken, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ModuleRoot = $(Get-Location) + ) + begin { + $CoverReport = [PSCustomObject]@{ + repo_token = $RepoToken + commit_sha = (git log --format="%H" HEAD -1) + git = Get-GitInfo + service_name = 'appveyor' + source_files = @() + } + } + + process { + # Find all files with hit commands -> These file have pester tests + $UsedFiles = $CodeCoverage.AnalyzedFiles | Where-Object { + $CodeCoverage.HitCommands.File -contains $_ + } + + foreach ($SourceFile in $UsedFiles) { + $Lines = (Get-Content -Path $SourceFile | Measure-Object).Count + Write-Verbose ("SourceFile: {0} | LinesCount: {1}" -f $SourceFile, $Lines) + $CoverageArray = @() + $Hits = 0 + $Missed = 0 + + for ($LinePointer = 1; $LinePointer -le $Lines; $LinePointer++) { + + # Get only hit commands from current src file + $curHits = $CodeCoverage.HitCommands | Where-Object { + $_.File -eq $SourceFile + } + [int]$Hits = ( + $curHits | Where-Object { + $_.Line -eq $LinePointer + } | Measure-Object + ).Count + + # again filter only missed commands from the curent file + $curMissed = $CodeCoverage.MissedCommands | Where-Object { + $_.File -eq $SourceFile + } + [int]$Missed = ( + $curMissed | Where-Object { + $_.Line -eq $LinePointer + } | Measure-Object + ).Count + + Write-Verbose ("SourceFile:{0} | Line: {1} | Hits: {2} | Missed: {3}" -f $SourceFile, $LinePointer, $Hits, $Missed) + if ((-not $Hits -gt 0) -and (-not $Missed -gt 0)) { + $CoverageArray += 'null' + } + else { + if ($Hits -gt 0) { + $CoverageArray += $Hits + } + elseif ($Missed -gt 0) { + $CoverageArray += 0 + } + } + } + # Get rid of the quotation + $CoverageArray = $CoverageArray -Replace '"', '' + $CoverageSourceFile = [PSCustomObject]@{ + name = $SourceFile.Replace($ModuleRoot, '').Replace('\', '/') + source_digest = (Get-FileHash -Path $SourceFile -Algorithm MD5).Hash + coverage = $CoverageArray + } + If ($CoverageSourceFile.Name.StartsWith('/')) { + $CoverageSourceFile.Name = $CoverageSourceFile.Name.Remove(0, 1) + } + $CoverReport.source_files += $CoverageSourceFile + } + + # Find all untested files to create a null coverage file + $UnUsedFiles = $CodeCoverage.AnalyzedFiles | Where-Object { + $CodeCoverage.HitCommands.File -notcontains $_ + } + + foreach ($UnUsedFile in $UnUsedFiles) { + $Lines = (Get-Content -Path $UnUsedFile | Measure-Object).Count + $CoverageArray = @() + for ($LinePointer = 1; $LinePointer -le $Lines; $LinePointer++) { + $CoverageArray += '0' + } + $CoverageSourceFile = [PSCustomObject]@{ + name = $UnUsedFile.Replace($ModuleRoot, '').Replace('\', '/') + source_digest = (Get-FileHash -Path $UnUsedFile -Algorithm MD5).Hash + coverage = $CoverageArray + } + if ($CoverageSourceFile.Name.StartsWith('/')) { + $CoverageSourceFile.Name = $CoverageSourceFile.Name.Remove(0, 1) + } + $CoverReport.source_files += $CoverageSourceFile + } + } + + end { + Write-Output $CoverReport + } +} + +function Publish-CoverageReport () { + [CmdletBinding()] + param( + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [PSCustomObject]$CoverageReport + ) + begin { + Add-Type -AssemblyName System.Net.Http + } + + process { + $CoverageJSON = ConvertTo-Json $CoverageReport -Depth 5 + # Try to fix null elements in coverage array. + $CoverageJSON = $CoverageJSON.Replace('"null"', 'null') + $stringContent = New-Object System.Net.Http.StringContent ($CoverageJSON) + $httpClient = New-Object System.Net.Http.Httpclient + $formdata = New-Object System.Net.Http.MultipartFormDataContent + $formData.Add($stringContent, "json_file", "coverage.json") + $result = $httpClient.PostAsync('https://coveralls.io/api/v1/jobs', $formData).Result + $content = $result.Content.ReadAsStringAsync() + } + + end { + Write-Output $Content + } +} diff --git a/tools/Travis.psm1 b/tools/Travis.psm1 new file mode 100644 index 0000000..d4a37f6 --- /dev/null +++ b/tools/Travis.psm1 @@ -0,0 +1,66 @@ +$Global:ProgressPreference = 'SilentlyContinue' + +$CALLSIGN = 'PSCredentialStore' +Write-Host ("Callsign is: {0}" -f $CALLSIGN) -ForegroundColor Black -BackgroundColor Yellow + +function Invoke-InstallDependencies { + [CmdletBinding()] + Param() + + process { + try { + Write-Host '===== Environment Vars: =====' -ForegroundColor Black -BackgroundColor Yellow + Get-ChildItem -Path Env: + + Write-Host -Message '===== Existing Variables: =====' -ForegroundColor Black -BackgroundColor Yellow + Get-Variable -Name * | Format-Table -AutoSize + + Get-PackageProvider -ListAvailable + Import-PackageProvider -Name 'NuGet' -MinimumVersion '2.8.5.208' -Verbose -Force + + Install-Module -Name 'Pester' -Scope CurrentUser -RequiredVersion '4.4.2' -Force -SkipPublisherCheck -AllowClobber -Verbose + Install-Module -Name 'posh-git' -Scope CurrentUser -RequiredVersion '1.0.0-beta2' -Force -SkipPublisherCheck -AllowClobber -AllowPrerelease -Verbose + + Import-Module -Name 'Pester', 'posh-git' -Verbose + } + catch { + $_.Exception.Message | Write-Error + throw 'Could not install the required dependencies!' + } + } +} + +function Invoke-UnitTests { + [CmdletBinding()] + Param() + + process { + + try { + Write-Host '===== Preload internal private functions =====' -ForegroundColor Black -BackgroundColor Yellow + + $Privates = Get-ChildItem -Path (Join-Path -Path $Env:TRAVIS_BUILD_DIR -ChildPath '/src/Private/*') -Include "*.ps1" -Recurse -ErrorAction Stop + foreach ($File in $Privates) { + if (Test-Path -Path $File.FullName) { + . $File.FullName + Write-Verbose -Message ('Private function dot-sourced: {0}' -f $File.FullName) -Verbose + } + else { + Write-Warning -Message ('Could not find file: {0} !' -f $File.FullName) + } + } + } + catch { + $_.Exception.Message | Write-Error + throw 'Could not load required private functions!' + } + + Write-Host '===== Running Pester =====' -ForegroundColor Black -BackgroundColor Yellow + $TestFiles = Get-ChildItem -Path (Join-Path -Path '.' -ChildPath './tests/*.Tests.ps1') -Recurse| Sort-Object -Property Name + $TestResults = Invoke-Pester -Script $TestFiles -ExcludeTag 'Disabled' -PassThru + + if ($TestResults.FailedCount -gt 0) { + throw ('{0} tests failed!' -f $TestResults.FailedCount) + } + } +} diff --git a/tools/travis.sh b/tools/travis.sh new file mode 100644 index 0000000..029896a --- /dev/null +++ b/tools/travis.sh @@ -0,0 +1,14 @@ +set -x +ulimit -n 4096 + +echo "TRAVIS_EVENT_TYPE value $TRAVIS_EVENT_TYPE" + +if [ $TRAVIS_EVENT_TYPE = cron ] || [ $TRAVIS_EVENT_TYPE = api ]; then + sudo pwsh -NoProfile -NonInteractive -c "Import-Module ./tools/Travis.psm1; + Invoke-InstallDependencies; + Invoke-UnitTests;" +else + sudo pwsh -NoProfile -NonInteractive -c "Import-Module ./tools/Travis.psm1; + Invoke-InstallDependencies; + Invoke-UnitTests;" +fi