diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..6993b5eb0e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,480 @@ +#(maint) Remove trailing whitespace +2456459bfd2094f4b32cb9267ad7eb7d2cb3eec0 + +#(maint) Add trailing newline to files +4b6656b10102acee4b7c958adc066571d9f04dd4 + +#(maint) Add BOM +a374d52a9f5cd3d48687ea25e0b7c059dfeb7d3f + +#(maint) Fix line endings +8face0175ac571d47ee405254dc0962d0ef1783c + +#(#2261) Updates License Headers Copyright to 2021 +3ac4e4e924de7302a6851096ffba091b2b1175f6 + +#(maint) Add 2018 to copyright year +0a86d5e5e78747f0ae00b5eb04916c19a0e7ba86 + +#(GH-1209) Add Chocolatey Software to copyright +8dc774eb615a760581b49913402bc3956d354205 + +#(maint) Corrected whitespace +44558390a70614440bc9c113e5c7da4576cfeaa4 + +#(maint) Corrected whitespace +57f966ff5b51cf43a56e9aa56f844384326ae0a1 + +#(maint) Corrected whitespace +fd81fcd6c48688c67e8f8e3a9cd2dd08126870b3 + +#(maint) Corrected whitespace +6cc246eb3d71d8f01e4a6f5e608fc836348ad19e + +#(maint) Corrected whitespace +f8b0c6abe5cae1ba8025e0556d8d7dc9d105e4cb + +#(maint) Whitespace changes +7c9e4405477449890cc12ad757df503ffc006e61 + +#(maint) Whitespace changes +00025cbc6df2640cb967d3539fdec1b259f77d45 + +#(maint) Whitespace changes +d7b2544b7fb627b9b4d2bead9c117e97ca85facd + +#(maint) Whitespace changes +22284f0f31358eee8100b91a19a4ec6d101f17bf + +#(maint) Whitespace changes +dc49f717c6c2f2ade3df292850c648028cd69f61 + +#(maint) Remove unnecessary whitespace +a7b1d03f37ede4e771e8ca45fd73717303a1a76b + +#(maint) Remove unnecessary whitespace +de61b1d1a19a7edd8e9b38921b3b29e54d55ac15 + +#(maint) Remove unnecessary whitespace +53694495ec6f0ff266c4a22b7329be1d770adc03 + +#(maint) Remove unnecessary whitespace +50a21c48e50c444a436b1be4dd5688c19fab9eac + +#(maint) Correct whitespace +9d4dd65a1fcfacf07fe4ccd9c63f69b4a3506f7c + +#(maint) Remove unnecessary whitespace +2a99e3873b1535060a2de8418c52854976552002 + +#(maint) Remove unnecessary whitespace +32461370879f26c1595083aeed8b49c73f6aaeb5 + +#(maint) Remove unnecessary whitespace +eb30cef11038767da4ac456c2372d66d09080c81 + +#(maint) Remove unnecessary whitespace +0d419905ce06cee00ade0f0ebf4ce1eb33b38240 + +#(maint) Remove whitespace +aab0406f70a46212bbb5d023e53a41c786a95ede + +#(maint) whitespace +6b4d50bf294f0cdb05ece1664a1e33d3095f9d5e + +#(maint) Correct whitespace +ba3c10b544d919ab39e21c558fed64008f835c34 + +#(maint) Corrected whitespace +44558390a70614440bc9c113e5c7da4576cfeaa4 + +#(maint) Correct whitespace +b2f54e3e66b919284c168509fce4e6e56f799218 + +#(maint) Correct whitespace +d91bf41319d4ca2ee48263eb46eca37ea09f210f + +#(maint) whitespace +968a84a9ebbe08c9b728799bed337ed561ddb4bf + +#(maint) Remove unnecessary whitespace +1a20a2e2e90d07e058887c397ec9a88e3c87279e + +#(maint) Formatting + whitespace fix +99fd2c2c9d714972bda8cfcf0748d4b27d3584e2 + +#(maint) Remove unnecessary whitespace +cf2862201e62edbfbfdf06c454b18ddd1e81e045 + +#(maint) Remove whitespace and fix formatting +d7b2d08232977971e55aa4ed851345880dc07517 + +#(maint) Whitespace +0efabf0b8309e9301e742441c20a12bbe3e5074c + +#(maint) Whitespace and formatting fix +ed46ca7fae1b8afd720bd6dc612cfdefb2a04edf + +#(maint) Remove unnecessary whitespace +742dacca1f32dea0723b16660faeef6dbdcf39a4 + +#(maint) Fix whitespace and formatting +0c4025b48aa62be48fd221918b785ac4ed73e07f + +#(maint) Formatting +f701feabf7f1deccaba335f486f3ca3a4056cccf + +#(maint) Formatting +9315a9ec54f0f24770f3184d8cbc1b4493607470 + +#(maint) Formatting + whitespace fix +99fd2c2c9d714972bda8cfcf0748d4b27d3584e2 + +#(maint) formatting +38edf1efd9d73e7a5f0502e898e917dd8ecaf242 + +#(maint) Formatting and whitespace changes +2bf2bcd88d9c9b33f2dd092884863365b3fcc55a + +#(maint) Formatting and whitespace changes +1007d8c760d75a63df23521bae612c9f6183c822 + +#(maint) formatting +179f0f32b4a5f646e5ada6401935627e91be6a98 + +#(maint) formatting +0d840334bb4cad0432089e11096769b6353e5c8e + +#(maint) formatting +3837ffdc36812fdbb030e938032517c3a5cd8ffc + +#(maint) formatting +0fe09728612a1ef29317aecf9d9cfa041817d3d8 + +#(maint) formatting +e0c753fc4da00c5863ff5d6b25b332d29f83f11f + +#(maint) formatting / order +1675a19d8b1c8ad6e3efb11a4202ff4dffe8557c + +#(maint) formatting +c8c28093ac68cff6aa59728ac1d1065996b96a13 + +#(maint) formatting +bca094debb96735e793468a6fcc0d59acf6734cd + +#(maint) formatting +d32982a22703e13b2e4708373b755c02211852d7 + +#(maint) formatting +fd63e4a8807bb352bd8b7c31f5aa43ed35278171 + +#(maint) formatting +865d7477f24e5dc81add0c1d3a587bd1374c4a76 + +#(maint) formatting +f5798180e7fd26fb9c3da343d2bdba6ed2798af0 + +#(maint) formatting +f9e586d12fcca2f2e42e43abd329fb4eb97626bc + +#(maint) formatting +5dcd104ccb9d38fe5e4977ab0c1382cc8703fc77 + +#(maint) formatting +68de28f738dbc2a6537a9c304a199ca5809bc18a + +#(maint) formatting/wording +75e8063ee322db789dae05ee65af97a22c86c21d + +#(maint) formatting +8bd20a0f6aeb577b48af6c4638de5896021532bb + +#(maint) formatting +3a0ee21a669656f56d661bec487085c422990f70 + +#(maint) formatting +1bcd6dc600542f42237779f2213d484a39d66f93 + +#(maint) formatting +939afa30b87728762f5409a6073f9b68bf50d5f0 + +#(maint) formatting +d25ef6dcd5f18852160912f47b460af56611eb62 + +#(maint) formatting +3da47609824464780f0707868a28c521dc9efdba + +#(maint) formatting +260c872954782cc987ee9cbfa3f55ce7e809e500 + +#(maint) formatting +48e668dc1e8ddc75d6135fb6721b00f7326af9bc + +#(maint) formatting +581db0177e43d6e2b9dd1852a6cef9f25df878dc + +#(maint) formatting +c51f9d301fb52cf6fd93a51cac1a55a84d36c1d7 + +#(maint) formatting +d12c94c799cd7c1904c14546f81e001fae44161f + +#(maint) formatting +728deda7ba4a4a46be371aa8da352a1bc597bb90 + +#(maint) formatting +70a6120723a0ffe8505ee238fbf1024f5bca3505 + +#(maint) formatting +1a9aa99f2c34c2ef4c9eed4a37ad213f75a92965 + +#(maint) formatting +e63773b5ab1aea63f8b514e30df9921847cbf55c + +#(maint) formatting +12230d4acafe72da79f3e0761e292440a5783bb6 + +#(maint) formatting +77be5d8192dc57c9523daf59f70da85cfdda876c + +#(maint) formatting +43b139c332bc664700963a0fd6d28e0349c10b47 + +#(maint) formatting methods / parameters in calls +0666e26fcfe1b95b7685cdcd0ff4bd9e9e51cda3 + +#(maint) formatting +186f9c739ff4654363ce675cfcd71ac7722a24a4 + +#(maint) formatting +74af77d178d0750c55262fd09d2ba1fcb5842b84 + +#(maint) formatting +ff97904b5c398349b738e16e643fc996c33c73f2 + +#(maint) formatting +f4ae1c9845cddbc83d75f09706b9c0b5e00d32f6 + +#(maint) formatting +0513e70ef2104f8f345e514f86b3202884a849ae + +#(maint) formatting +be903e4e5585f551aebde1e09f93b46612283b25 + +#(maint) formatting +e2fa59116b8e42ef44f611b9b14362d51db6c5ba + +#(maint) formatting / add message consistency +2854c5ae8e6aa3fbe6f5b5fe51a59190c886cc72 + +#(maint) formatting +c3ae26769cd7a023cea0e3c0561752bedd05e5da + +#(maint) formatting +d51e259d38312c28ea6e5954ebf7ca4311820699 + +#(maint) formatting +7709753f8804df22d615398f4cc3d975734af34f + +#(maint) formatting +31db2695841501784d0a05530d16ec6a7397dc4a + +#(maint) formatting +e5726d7340a07e0c9127f3518ad95f7d506a5392 + +#(maint) formatting +a7d92f53263acf6205965b65bfafa4282563a8cf + +#(maint) formatting +c819d7bf07b8e576a22b92ecb448b1497b8ab094 + +#(maint) formatting +727879cab1a647af92b701146c26f263b11a78ae + +#(maint) formatting +252f7c5152cd6c8c254a67d036cd6ea350ba747a + +#(maint) formatting +40095903aebcbe5304c9bc82196cd68f437c547c + +#(maint) formatting +4643bab03d187a381e3a7c2f6d206dda215afae6 + +#(maint) formatting +b74dbec14fbf5d11f6575542976ce85030c75558 + +#(maint) formatting +4f3454295d332f4b41ad84224d0e5b5dc6e20eb8 + +#(maint) formatting +5f6439a974db86a729ea449d6d72b295a4ced72d + +#(maint) formatting +69f3466674907be27b2929173ac09bee0b8a10ca + +#(maint) formatting +09765f6626458f5fb491f43bc24f0755531d9c88 + +#(maint) formatting +8906ea82aa689e0557e03bb6348f3aa4688d37a7 + +#(maint) formatting +da4cb2a646c35c0c976085f6d8362c43af696fd8 + +#(maint) formatting +b09644e00c925d6c17ad01a578fb726d07ccb4a4 + +#(maint) formatting +7ae1939f734615357247c6a518c52a76a9869077 + +#(maint) formatting +5601e2db63aefa02411b7c40aadd789942e81e7a + +#(maint) formatting +c8d9630c8698f9738b423198d9119d1aac8b2aed + +#(maint) formatting +763ac498364ecd6b9ac6240d70c57ccccca62849 + +#(maint) formatting +b7c619f10e42a8dd57b6f42c59b735d74e1890c3 + +#(maint) formatting +9c1bfe1d30e958e4113289bafbd8b1954ad8c3f8 + +#(maint) formatting +1dffc0a958a5d85fb0b1ec2506052a88190c01a7 + +#(maint) formatting +c8bec4424243adf6d4201fb8f6e940e468d00b0b + +#(maint) formatting +998bc44116cb3054ac5717fcd4547bcb03db55d6 + +#(maint) formatting +346c050025dc4c51800cc8cd4985d81710c40ebb + +#(maint) formatting +cd98ee0053aae1c29485bd5f269fcdff7aaa370c + +#(maint) formatting +e5ea5a6ca3f58062a2ba9e04a2b616ee036b6cc1 + +#(maint) formatting/comment +231ac31dfab285e740b50ab583feaf376d3ccd9c + +#(maint) formatting +580a66467783c436fcdd3cdb74a4ee8e94b73fb1 + +#(maint) formatting +855b4abc3e348f669091704c3951d7f5c7970956 + +#(maint) formatting +5765e5e80d1dcd31d55c671e145037d919509015 + +#(maint) formatting +9945cd0bafd3983bc10903d0ec9b01d48210afdd + +#(maint) formatting +e6ad7e7f90fa28417602ec22349add8750aa50ac + +#(maint) formatting +87c8c5b1676131b3bf2823142921cb1c828e83b0 + +#(maint) formatting +2b68dae5efb93699dbbc65e6761fd1d5f7a362a2 + +#(maint) formatting +e4f145e4bcc16d2d0e3360a4e4b253175483fe7b + +#(maint) formatting +016c6914335198e78305db0691ae9b800d14e13c + +#(maint) formatting +309f33ecc8d454d12d3398cdccba77a3658ba106 + +#(maint) formatting +35c98096055d3c034848ffa54fbbc0d284d0e037 + +#(maint) formatting +e199c22c2b304215835b27778dbbcc63a14b529a + +#(maint) formatting +abdea1856bcb370d3b1ffe782922d793ba9ecb3e + +#(maint) formatting +f10c55d3fc860b66f7892930b493eda2953e0419 + +#(maint) formatting +3ff7adb0ecb1b85da9340693f11d9699ae2d50c3 + +#(maint) formatting +4f39b7439e045ed2b65fe72741b753afb3b4d33d + +#(maint) formatting +f1df436680acac307d489a8402390891d8c32573 + +#(maint) formatting +ddf8d2f3692a9f1de472965aa1f5f619acb23c50 + +#(maint) formatting +cee189ccdd0e0a4169044a1d847d0fb8226d4c67 + +#(maint) formatting/spacing +32dc4420f35a55375da9d253e2ee591b4d72a674 + +#(maint) formatting +67fbbcf5b4f2828e7c3910730e042b90181e0415 + +#(maint) formatting +13451a7a841f353365953454f91503b0af79392d + +#(maint) formatting +26447f974f5853866d73e46f71af378a7834bdac + +#(maint) formatting +a808f2f2e07cbbbcac021de7e4c74d90c63d523c + +#(maint) formatting +a2a6eb98e7add1c9646e9f86a4614ee22a78f950 + +#(maint) formatting +a2d026d4ef173d68132c2e51c8abbea408eb9f3e + +#(maint) formatting +fea75e87f0ef7d64eb823f4a091f958594c4fe4e + +#(maint) formatting +e3d3a9fc2c3bf3d54273bd51991fd44eca9bae24 + +#(maint) formatting +e387053ab1502088e92ad4a72f65f6da88f4cae4 + +#(maint) formatting +ebc811ae6e10693e07b8bab406bcf7f76711209a + +#(maint) formatting +18b33736da35bde7419e8a2b2a631aafd0a3ba36 + +#(maint) formatting +785f1e970ca2d43de6d5e5181dddc8c9c233fdc6 + +#(maint) formatting +97458f8877f07c6cea87d2702b98ccb9f36cb712 + +#(maint) formatting +5e29a252b52ef4c5c5324844d7796845b00db2d8 + +#(maint) formatting +b19c8c24b08d3b4a8d2b2b88d46edead71d0e31a + +#(maint) formatting +e9515c247972ecf22ece68e080ae96ef0e8ebd6c + +#(maint) formatting of xml comments +5e7bdb4143e89605ce887543b4ba74f6af488e3c + +#(maint) formatting +f6393c625416250e87294bb4f20b94495a1ed923 + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c40ca815c..fe5ce3850f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: path: tools key: ${{ runner.os }}-tools-${{ hashFiles('recipe.cake') }} - name: Test with NUnit on .Net Framework - run: .\build.bat --verbosity=diagnostic --target=test --testExecutionType=all + run: .\build.bat --verbosity=diagnostic --target=test --testExecutionType=all --shouldRunOpenCover=false - name: Upload Windows build results uses: actions/upload-artifact@v2 # Always upload build results diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2ea55cd1e..dacee223c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,6 +117,7 @@ Start with [Prerequisites](#prerequisites) and make sure you can sign the Contri * `git config merge.ff false` * `git config merge.log true` * `git config fetch.prune true` + * `git config blame.ignoreRevsFile .git-blame-ignore-revs --local` 1. From there you create a branch named specific to the feature. 1. In the branch you do work specific to the feature. 1. For committing the code, please see [Prepare Commits](#prepare-commits). diff --git a/GenerateDocs.ps1 b/GenerateDocs.ps1 index a8eca1f319..62f37ee0e1 100644 --- a/GenerateDocs.ps1 +++ b/GenerateDocs.ps1 @@ -489,7 +489,6 @@ Chocolatey makes a number of environment variables available (You can access any * ChocolateyPackageName - The name of the package, equivalent to the `` field in the nuspec (0.9.9+) * ChocolateyPackageTitle - The title of the package, equivalent to the `` field in the nuspec (0.10.1+) * ChocolateyPackageVersion - The version of the package, equivalent to the `<version />` field in the nuspec (0.9.9+) - * ChocolateyPackageFolder - The top level location of the package folder - the folder where Chocolatey has downloaded and extracted the NuGet package, typically `C:\ProgramData\chocolatey\lib\packageName`. #### Advanced Environment Variables @@ -502,7 +501,7 @@ The following are more advanced settings: * OS_VERSION - The version of OS, like 6.1 something something for Windows. (0.9.9+) * OS_NAME - The reported name of the OS. (0.9.9+) * IS_PROCESSELEVATED = Is the process elevated? (0.9.9+) - * ChocolateyToolsLocation - formerly 'ChocolateyBinRoot' ('ChocolateyBinRoot' will be removed with Chocolatey v2.0.0), this is where tools being installed outside of Chocolatey packaging will go. (0.9.10+) + * ChocolateyPackageInstallLocation - Install location of the software that the package installs. Displayed at the end of the package install. (0.9.10+) #### Set By Options and Configuration @@ -533,8 +532,8 @@ The following are experimental or use not recommended: #### Not Useful Or Anti-Pattern If Used - * ChocolateyInstallOverride = Not for use in package automation scripts. Based on `--override-arguments` being passed. (0.9.9+) - * ChocolateyInstallArguments = The installer arguments meant for the native installer. You should use chocolateyPackageParameters instead. Based on `--install-arguments` being passed. (0.9.9+) + * ChocolateyInstallOverride - Not for use in package automation scripts. Based on `--override-arguments` being passed. (0.9.9+) + * ChocolateyInstallArguments - The installer arguments meant for the native installer. You should use chocolateyPackageParameters instead. Based on `--install-arguments` being passed. (0.9.9+) * ChocolateyIgnoreChecksums - Was `--ignore-checksums` passed or the feature `checksumFiles` turned off? (0.9.9.9+) * ChocolateyAllowEmptyChecksums - Was `--allow-empty-checksums` passed or the feature `allowEmptyChecksums` turned on? (0.10.0+) * ChocolateyAllowEmptyChecksumsSecure - Was `--allow-empty-checksums-secure` passed or the feature `allowEmptyChecksumsSecure` turned on? (0.10.0+) @@ -551,6 +550,8 @@ The following are experimental or use not recommended: * http_proxy - Set by original `http_proxy` passthrough, or same as `ChocolateyProxyLocation` if explicitly set. (0.10.4+) * https_proxy - Set by original `https_proxy` passthrough, or same as `ChocolateyProxyLocation` if explicitly set. (0.10.4+) * no_proxy- Set by original `no_proxy` passthrough, or same as `ChocolateyProxyBypassList` if explicitly set. (0.10.4+) + * ChocolateyPackageFolder - Not for use in package automation scripts. Recommend using `$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"` as per template generated by `choco new` + * ChocolateyToolsLocation - Not for use in package automation scripts. Recommend using Get-ToolsLocation instead '@ $global:powerShellReferenceTOC | Out-File $fileName -Encoding UTF8 -Force diff --git a/Invoke-Tests.ps1 b/Invoke-Tests.ps1 index f4f486cbf3..b11fb8524c 100644 --- a/Invoke-Tests.ps1 +++ b/Invoke-Tests.ps1 @@ -93,7 +93,17 @@ try { Verbosity = 'Minimal' } Filter = @{ - ExcludeTag = 'Background', 'Licensed', 'CCM', 'WIP', 'NonAdmin' + ExcludeTag = @( + 'Background' + 'Licensed' + 'CCM' + 'WIP' + 'NonAdmin' + 'Internal' + if (-not $env:VM_RUNNING -and -not $env:TEST_KITCHEN) { + 'VMOnly' + } + ) } } diff --git a/TESTING.md b/TESTING.md index 7f1a7cb87c..8fd01f0ca6 100644 --- a/TESTING.md +++ b/TESTING.md @@ -14,11 +14,13 @@ The NUnit tests get run automatically when you run `./build.bat` or `./build.sh` ### NUnit Integration Tests -If you need to run the integration tests, you can do so using: `./build.bat --target=test-nunit --exclusive --testExecutionType=integration`, or `./build.sh --target=test-nunit --exclusive --testExecutionType=integration`. +If you need to run the integration tests, you can do so using: `./build.bat --target=test-nunit --exclusive --testExecutionType=integration --shouldRunOpenCover=false`, or `./build.sh --target=test-nunit --exclusive --testExecutionType=integration --shouldRunOpenCover=false`. ### All NUnit Integration Tests -If you need to run all the tests, you can do so using: `./build.bat --target=test-nunit --exclusive --testExecutionType=all`, or `./build.sh --target=test-nunit --exclusive --testExecutionType=all`. +If you need to run all the tests, you can do so using: `./build.bat --target=test-nunit --exclusive --testExecutionType=all --shouldRunOpenCover=false`, or `./build.sh --target=test-nunit --exclusive --testExecutionType=all --shouldRunOpenCover=false`. + +The `shouldRunOpenCover` argument is required when running the integration tests because some of the integration tests rely on the standard output and error output, which is not available when run via OpenCover. This switch changes the NUnit tests to run on NUnit directly, instead of on NUnit via OpenCover. ### Skipping NUnit Tests diff --git a/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 b/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 index c507bb363b..a6bdfd8ab3 100644 --- a/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 +++ b/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 @@ -288,7 +288,7 @@ param( $ErrorActionPreference = 'Stop' try { # get current acl - $acl = (Get-Item $folder).GetAccessControl('Access,Owner') + $acl = Get-Acl $folder Write-Debug "Removing existing permissions." $acl.Access | ForEach-Object { $acl.RemoveAccessRuleAll($_) } @@ -334,17 +334,17 @@ param( $acl.SetAccessRuleProtection($true, $false) # enact the changes against the actual - (Get-Item $folder).SetAccessControl($acl) + Set-Acl -Path $folder -AclObject $acl # set an explicit append permission on the logs folder Write-Debug "Allow users to append to log files." $logsFolder = "$folder\logs" Create-DirectoryIfNotExists $logsFolder - $logsAcl = (Get-Item $logsFolder).GetAccessControl('Access') + $logsAcl = Get-Acl $logsFolder $usersAppendAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($builtinUsers, $rightsWrite, [Security.AccessControl.InheritanceFlags]::ObjectInherit, [Security.AccessControl.PropagationFlags]::InheritOnly, "Allow") $logsAcl.SetAccessRule($usersAppendAccessRule) $logsAcl.SetAccessRuleProtection($false, $true) - (Get-Item $logsFolder).SetAccessControl($logsAcl) + Set-Acl -Path $logsFolder -AclObject $logsAcl } catch { Write-ChocolateyWarning "Not able to set permissions for $folder." } diff --git a/recipe.cake b/recipe.cake index 85d1481510..c192390fb4 100644 --- a/recipe.cake +++ b/recipe.cake @@ -1,4 +1,4 @@ -#load nuget:?package=Chocolatey.Cake.Recipe&version=0.13.2 +#load nuget:?package=Chocolatey.Cake.Recipe&version=0.16.0 /////////////////////////////////////////////////////////////////////////////// // TOOLS diff --git a/src/chocolatey.console/Program.cs b/src/chocolatey.console/Program.cs index 7b2a505422..20cf37fffe 100644 --- a/src/chocolatey.console/Program.cs +++ b/src/chocolatey.console/Program.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,20 +21,25 @@ namespace chocolatey.console using System.IO; using System.Linq; using System.Reflection; + using chocolatey.infrastructure.information; using infrastructure.app; using infrastructure.app.builders; using infrastructure.app.configuration; using infrastructure.app.runners; using infrastructure.commandline; - using infrastructure.configuration; using infrastructure.extractors; using infrastructure.licensing; using infrastructure.logging; using infrastructure.registration; using infrastructure.tolerance; + using SimpleInjector; + #if !NoResources + using resources; + #endif + using Assembly = infrastructure.adapters.Assembly; using Console = System.Console; using Environment = System.Environment; @@ -65,11 +70,17 @@ private static void Main(string[] args) "LogFileOnly".Log().Info(() => "".PadRight(60, '=')); - config = Config.get_configuration_settings(); + config = container.GetInstance<ChocolateyConfiguration>(); var fileSystem = container.GetInstance<IFileSystem>(); var warnings = new List<string>(); + if (license.AssemblyLoaded && !is_licensed_assembly_loaded(container)) + { + license.AssemblyLoaded = false; + license.IsCompatible = false; + } + ConfigurationBuilder.set_up_configuration( args, config, @@ -78,7 +89,7 @@ private static void Main(string[] args) warning => { warnings.Add(warning); } ); - if (license.is_licensed_version() && !license.IsCompatible && !config.DisableCompatibilityChecks) + if (license.AssemblyLoaded && license.is_licensed_version() && !license.IsCompatible && !config.DisableCompatibilityChecks) { write_warning_for_incompatible_versions(); } @@ -91,7 +102,7 @@ private static void Main(string[] args) if (!string.IsNullOrWhiteSpace(config.AdditionalLogFileLocation)) { - Log4NetAppenderConfiguration.configure_additional_log_file(fileSystem.get_full_path(config.AdditionalLogFileLocation)); + Log4NetAppenderConfiguration.configure_additional_log_file(fileSystem.get_full_path(config.AdditionalLogFileLocation)); } report_version_and_exit_if_requested(args, config); @@ -137,7 +148,7 @@ private static void Main(string[] args) remove_old_chocolatey_exe(fileSystem); - AssemblyFileExtractor.extract_all_resources_to_relative_directory(fileSystem, Assembly.GetAssembly(typeof(Program)), ApplicationParameters.InstallLocation, new List<string>(), "chocolatey.console", throwError:false); + AssemblyFileExtractor.extract_all_resources_to_relative_directory(fileSystem, Assembly.GetAssembly(typeof(Program)), ApplicationParameters.InstallLocation, new List<string>(), "chocolatey.console", throwError: false); //refactor - thank goodness this is temporary, cuz manifest resource streams are dumb IList<string> folders = new List<string> { @@ -171,7 +182,7 @@ private static void Main(string[] args) } finally { - if (license != null && license.is_licensed_version() && !license.IsCompatible && config != null && !config.DisableCompatibilityChecks) + if (license != null && license.AssemblyLoaded && license.is_licensed_version() && !license.IsCompatible && config != null && !config.DisableCompatibilityChecks) { write_warning_for_incompatible_versions(); } @@ -186,6 +197,25 @@ private static void Main(string[] args) } } + private static bool is_licensed_assembly_loaded(Container container) + { + var allExtensions = container.GetAllInstances<ExtensionInformation>(); + + foreach (var extension in allExtensions) + { + if (extension.Name.is_equal_to("chocolatey.licensed")) + { + return extension.Status == ExtensionStatus.Enabled || extension.Status == ExtensionStatus.Loaded; + } + } + + // We will be going by an assumption that it has been loaded in this case. + // This is mostly to prevent that the licensed extension won't be disabled + // if it has been loaded using old method. + + return true; + } + private static void warn_on_nuspec_or_nupkg_usage(string[] args, ChocolateyConfiguration config) { var commandLine = Environment.CommandLine; @@ -196,6 +226,7 @@ private static void warn_on_nuspec_or_nupkg_usage(string[] args, ChocolateyConfi } private static ResolveEventHandler _handler = null; + private static void add_assembly_resolver() { _handler = (sender, args) => @@ -208,8 +239,40 @@ private static void add_assembly_resolver() var chocolateyPublicKey = ApplicationParameters.UnofficialChocolateyPublicKey; #endif + if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyPublicKey)) + { + // Check if it is already loaded + var resolvedAssembly = AssemblyResolution.resolve_existing_assembly(requestedAssembly.Name, chocolateyPublicKey); + + if (resolvedAssembly != null) + { + return resolvedAssembly.UnderlyingType; + } + + if (Directory.Exists(ApplicationParameters.ExtensionsLocation)) + { + foreach (var extensionDll in Directory.EnumerateFiles(ApplicationParameters.ExtensionsLocation, requestedAssembly.Name + ".dll", SearchOption.AllDirectories)) + { + try + { + resolvedAssembly = AssemblyResolution.load_assembly(requestedAssembly.Name, extensionDll, chocolateyPublicKey); + + if (resolvedAssembly != null) + { + return resolvedAssembly.UnderlyingType; + } + } + catch (Exception ex) + { + // This catch statement is empty on purpose, we do + // not want to do anything if it fails to load. + } + } + } + } + // There are things that are ILMerged into Chocolatey. Anything with - // the right public key except licensed should use the choco/chocolatey assembly + // the right public key except extensions should use the choco/chocolatey assembly if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyPublicKey) && !requestedAssembly.Name.is_equal_to(ApplicationParameters.LicensedChocolateyAssemblySimpleName) && !requestedAssembly.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) diff --git a/src/chocolatey.console/chocolatey.console.csproj b/src/chocolatey.console/chocolatey.console.csproj index fbd5c3f19a..71da181e4b 100644 --- a/src/chocolatey.console/chocolatey.console.csproj +++ b/src/chocolatey.console/chocolatey.console.csproj @@ -133,8 +133,8 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\..\lib\Chocolatey-NuGet.Core.2.11.0.20211014\lib\net4\NuGet.Core.dll</HintPath> </Reference> - <Reference Include="SimpleInjector"> - <HintPath>..\packages\SimpleInjector.2.5.0\lib\net40-client\SimpleInjector.dll</HintPath> + <Reference Include="SimpleInjector, Version=2.8.3.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> + <HintPath>..\packages\SimpleInjector.2.8.3\lib\net40-client\SimpleInjector.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.DataAnnotations" /> diff --git a/src/chocolatey.console/packages.config b/src/chocolatey.console/packages.config index 0bc63cb34d..502c8921c0 100644 --- a/src/chocolatey.console/packages.config +++ b/src/chocolatey.console/packages.config @@ -1,8 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> - <packages> <package id="AlphaFS" version="2.1.3" targetFramework="net40" /> <package id="log4net" version="2.0.12" targetFramework="net40" /> <package id="Microsoft.Web.Xdt" version="2.1.1" targetFramework="net40" /> - <package id="SimpleInjector" version="2.5.0" targetFramework="net40" /> + <package id="SimpleInjector" version="2.8.3" targetFramework="net40" /> </packages> \ No newline at end of file diff --git a/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 b/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 index 6bea170b55..7557d347c9 100644 --- a/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 +++ b/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 @@ -4,7 +4,9 @@ [switch] $overrideArgs = $false, [alias("x86")][switch] $forceX86 = $false, [alias("params","parameters","pkgParams")][string]$packageParameters = '', - [string]$packageScript + [string]$packageScript, + [string[]]$preRunHookScripts, + [string[]]$postRunHookScripts ) $global:DebugPreference = "SilentlyContinue" @@ -13,7 +15,7 @@ $global:VerbosePreference = "SilentlyContinue" if ($env:ChocolateyEnvironmentVerbose -eq 'true') { $global:VerbosePreference = "Continue"; $verbosity = $true } Write-Debug '---------------------------Script Execution---------------------------' -Write-Debug "Running 'ChocolateyScriptRunner' for $($env:packageName) v$($env:packageVersion) with packageScript `'$packageScript`', packageFolder:`'$($env:packageFolder)`', installArguments: `'$installArguments`', packageParameters: `'$packageParameters`'," +Write-Debug "Running 'ChocolateyScriptRunner' for $($env:packageName) v$($env:packageVersion) with packageScript '$packageScript', packageFolder:'$($env:packageFolder)', installArguments: '$installArguments', packageParameters: '$packageParameters', preRunHookScripts: '$preRunHookScripts', postRunHookScripts: '$postRunHookScripts'," ## Set the culture to invariant $currentThread = [System.Threading.Thread]::CurrentThread; @@ -45,8 +47,17 @@ $7zip = Join-Path $chocoTools '7z.exe' $ShimGen = Join-Path $chocoTools 'shimgen.exe' $checksumExe = Join-Path $chocoTools 'checksum.exe' -Write-Debug "Running `'$packageScript`'"; -& "$packageScript" +if ($PSBoundParameters.ContainsKey('preRunHookScripts')) { + foreach ($prehookscript in $preRunHookScripts) { + Write-Debug "Running Pre-Run Hook '$prehookscript'"; + & "$prehookscript" + } +} + +if ($packageScript) { + Write-Debug "Running package script '$packageScript'"; + & "$packageScript" +} $scriptSuccess = $? $lastExecutableExitCode = $LASTEXITCODE @@ -71,6 +82,13 @@ if ($exitCode -ne $null -and $exitCode -ne '' -and $exitCode -ne 0) { Set-PowerShellExitCode $exitCode } +if ($PSBoundParameters.ContainsKey('postRunHookScripts')) { + foreach ($posthookscript in $postRunHookScripts) { + Write-Debug "Running Post-Run Hook '$posthookscript'"; + & "$posthookscript" + } +} + Write-Debug '----------------------------------------------------------------------' Exit $exitCode \ No newline at end of file diff --git a/src/chocolatey.resources/helpers/functions/Get-ChocolateyUnzip.ps1 b/src/chocolatey.resources/helpers/functions/Get-ChocolateyUnzip.ps1 index 6425e3c289..d4acdadeef 100644 --- a/src/chocolatey.resources/helpers/functions/Get-ChocolateyUnzip.ps1 +++ b/src/chocolatey.resources/helpers/functions/Get-ChocolateyUnzip.ps1 @@ -75,6 +75,14 @@ folder and its contents will be extracted to the destination. OPTIONAL - This will facilitate logging unzip activity for subsequent uninstalls +.PARAMETER DisableLogging +OPTIONAL - This disables logging of the extracted items. It speeds up +extraction of archives with many files. + +Usage of this parameter will prevent Uninstall-ChocolateyZipPackage +from working, extracted files will have to be cleaned up with +Remove-Item or a similar command instead. + .PARAMETER IgnoredArguments Allows splatting with arguments that do not apply. Do not use directly. @@ -93,6 +101,7 @@ param( [parameter(Mandatory=$false, Position=2)][string] $specificFolder, [parameter(Mandatory=$false, Position=3)][string] $packageName, [alias("file64")][parameter(Mandatory=$false)][string] $fileFullPath64, + [parameter(Mandatory=$false)][switch] $disableLogging, [parameter(ValueFromRemainingArguments = $true)][Object[]] $ignoredArguments ) @@ -156,7 +165,12 @@ param( } $workingDirectory = $workingDirectory.ProviderPath - $params = "x -aoa -bd -bb1 -o`"$destinationNoRedirection`" -y `"$fileFullPathNoRedirection`"" + $loggingParam = '-bb1' + if ($disableLogging) { + $loggingParam = '-bb0' + } + + $params = "x -aoa -bd $loggingParam -o`"$destinationNoRedirection`" -y `"$fileFullPathNoRedirection`"" if ($specificfolder) { $params += " `"$specificfolder`"" } @@ -219,7 +233,7 @@ param( Set-PowerShellExitCode $exitCode Write-Debug "Command ['$7zip' $params] exited with `'$exitCode`'." - if ($zipExtractLogFullPath) { + if ($zipExtractLogFullPath -and -not $disableLogging) { Set-Content $zipExtractLogFullPath $global:zipFileList.ToString() -Encoding UTF8 -Force } diff --git a/src/chocolatey.resources/helpers/functions/Get-WebHeaders.ps1 b/src/chocolatey.resources/helpers/functions/Get-WebHeaders.ps1 index 73fe30355d..12c0d9c97b 100644 --- a/src/chocolatey.resources/helpers/functions/Get-WebHeaders.ps1 +++ b/src/chocolatey.resources/helpers/functions/Get-WebHeaders.ps1 @@ -24,9 +24,6 @@ This is a low-level function that is used by Chocolatey to get the headers for a request/response to better help when getting and validating internet resources. -.NOTES -Not recommended for use in package scripts. - .INPUTS None diff --git a/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 b/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 index 12ebe80d29..e021144a69 100644 --- a/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 +++ b/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 @@ -151,6 +151,14 @@ Will be used for Url64bit if Url64bit is empty. Available in 0.10.7+. This parameter provides compatibility, but should not be used directly and not with the community package repository until January 2018. +.PARAMETER DisableLogging +OPTIONAL - This disables logging of the extracted items. It speeds up +extraction of archives with many files. + +Usage of this parameter will prevent Uninstall-ChocolateyZipPackage +from working, extracted files will have to be cleaned up with +Remove-Item or a similar command instead. + .PARAMETER IgnoredArguments Allows splatting with arguments that do not apply. Do not use directly. @@ -191,6 +199,7 @@ param( [parameter(Mandatory=$false)][hashtable] $options = @{Headers=@{}}, [alias("fileFullPath")][parameter(Mandatory=$false)][string] $file = '', [alias("fileFullPath64")][parameter(Mandatory=$false)][string] $file64 = '', + [parameter(Mandatory=$false)][switch] $disableLogging, [parameter(ValueFromRemainingArguments = $true)][Object[]] $ignoredArguments ) @@ -213,5 +222,5 @@ param( } $filePath = Get-ChocolateyWebFile $packageName $downloadFilePath $url $url64bit -checkSum $checkSum -checksumType $checksumType -checkSum64 $checkSum64 -checksumType64 $checksumType64 -options $options -getOriginalFileName - Get-ChocolateyUnzip "$filePath" $unzipLocation $specificFolder $packageName + Get-ChocolateyUnzip "$filePath" $unzipLocation $specificFolder $packageName -disableLogging:$disableLogging } diff --git a/src/chocolatey.tests.integration/Scenario.cs b/src/chocolatey.tests.integration/Scenario.cs index afa6dbf6fc..d6dce1240a 100644 --- a/src/chocolatey.tests.integration/Scenario.cs +++ b/src/chocolatey.tests.integration/Scenario.cs @@ -49,6 +49,7 @@ public static void reset(ChocolateyConfiguration config) string badPackagesPath = get_package_install_path() + "-bad"; string backupPackagesPath = get_package_install_path() + "-bkp"; string shimsPath = ApplicationParameters.ShimsLocation; + string hooksPath = ApplicationParameters.HooksLocation; _fileSystem.delete_directory_if_exists(config.CacheLocation, recursive: true, overrideAttributes: true); _fileSystem.delete_directory_if_exists(config.Sources, recursive: true, overrideAttributes: true); @@ -58,6 +59,7 @@ public static void reset(ChocolateyConfiguration config) _fileSystem.delete_directory_if_exists(backupPackagesPath, recursive: true, overrideAttributes: true); _fileSystem.delete_directory_if_exists(_fileSystem.combine_paths(get_top_level(), ".chocolatey"), recursive: true, overrideAttributes: true); _fileSystem.delete_directory_if_exists(_fileSystem.combine_paths(get_top_level(), "extensions"), recursive: true, overrideAttributes: true); + _fileSystem.delete_directory_if_exists(hooksPath, recursive: true, overrideAttributes: true); _fileSystem.create_directory(config.CacheLocation); _fileSystem.create_directory(config.Sources); @@ -93,6 +95,7 @@ public static void install_package(ChocolateyConfiguration config, string packag installConfig.PackageNames = packageId; installConfig.Version = version; + installConfig.CommandName = CommandNameType.install.to_string(); _service.install_run(installConfig); NUnitSetup.MockLogger.Messages.Clear(); diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index 950ac93c6a..e4a30ea2d9 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -71,11 +71,8 @@ <Reference Include="Should, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Should.1.1.12.0\lib\Should.dll</HintPath> </Reference> - <Reference Include="SimpleInjector, Version=2.5.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> - <HintPath>..\packages\SimpleInjector.2.5.0\lib\net40-client\SimpleInjector.dll</HintPath> - </Reference> - <Reference Include="SimpleInjector.Diagnostics"> - <HintPath>..\packages\SimpleInjector.2.5.0\lib\net40-client\SimpleInjector.Diagnostics.dll</HintPath> + <Reference Include="SimpleInjector, Version=2.8.3.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> + <HintPath>..\packages\SimpleInjector.2.8.3\lib\net40-client\SimpleInjector.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Configuration" /> @@ -243,6 +240,18 @@ <None Include="context\exactpackage\exactpackage\1.0.0\exactpackage.nuspec"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> + <None Include="context\portablepackage\1.0.0\portablepackage.nuspec"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\portablepackage\1.0.0\tools\Casemismatch.exe.ignore"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\portablepackage\1.0.0\tools\graphical.exe.gui"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\portablepackage\1.0.0\tools\not.installed.exe.ignore"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> <None Include="context\installpackage\1.0.0\installpackage.nuspec"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> @@ -351,6 +360,168 @@ <None Include="context\nonterminatingerror\1.0\tools\chocolateyInstall.ps1"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\dontrun.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-beforemodify-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-beforemodify-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-beforemodify-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-install-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-install-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-install-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-uninstall-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-uninstall-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-uninstall-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-upgrade-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-upgrade-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\post-upgrade-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-beforemodify-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-beforemodify-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-beforemodify-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-install-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-install-doesnotexist.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-install-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-install-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-uninstall-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-uninstall-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-uninstall-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-upgrade-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-upgrade-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\hook\pre-upgrade-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\1.0.0\scriptpackage.hook.nuspec"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\dontrun.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-beforemodify-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-beforemodify-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-beforemodify-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-install-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-install-doesnotexist.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-install-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-install-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-uninstall-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-uninstall-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-uninstall-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-upgrade-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-upgrade-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\post-upgrade-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-beforemodify-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-beforemodify-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-beforemodify-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-install-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-install-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-install-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-uninstall-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-uninstall-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-uninstall-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-upgrade-all.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-upgrade-installpackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\hook\pre-upgrade-upgradepackage.ps1"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\scriptpackage.hook\2.0.0\scriptpackage.hook.nuspec"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> <None Include="context\testing.packages.config"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> @@ -505,6 +676,18 @@ <None Include="context\upgradepackage\1.1.1-beta\tools\graphical.exe"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> + <None Include="context\portablepackage\1.0.0\tools\casemismatch.exe"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\portablepackage\1.0.0\tools\console.exe"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\portablepackage\1.0.0\tools\graphical.exe"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Include="context\portablepackage\1.0.0\tools\not.installed.exe"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> <Content Include="infrastructure\filesystem\CopyMe.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> diff --git a/src/chocolatey.tests.integration/context/portablepackage/1.0.0/portablepackage.nuspec b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/portablepackage.nuspec new file mode 100644 index 0000000000..89aea19ad9 --- /dev/null +++ b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/portablepackage.nuspec @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>portablepackage</id> + <version>1.0.0</version> + <title>portablepackage + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + portablepackage admin + + + + + \ No newline at end of file diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/Casemismatch.exe.ignore b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/Casemismatch.exe.ignore similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/Casemismatch.exe.ignore rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/Casemismatch.exe.ignore diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/casemismatch.exe b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/casemismatch.exe similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/casemismatch.exe rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/casemismatch.exe diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/console.exe b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/console.exe similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/console.exe rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/console.exe diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/graphical.exe b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/graphical.exe similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/graphical.exe rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/graphical.exe diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/graphical.exe.gui b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/graphical.exe.gui similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/graphical.exe.gui rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/graphical.exe.gui diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/not.installed.exe b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/not.installed.exe similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/not.installed.exe rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/not.installed.exe diff --git a/tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/not.installed.exe.ignore b/src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/not.installed.exe.ignore similarity index 100% rename from tests/chocolatey-tests/testpackages/installpackage/1.0.0/tools/not.installed.exe.ignore rename to src/chocolatey.tests.integration/context/portablepackage/1.0.0/tools/not.installed.exe.ignore diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/dontrun.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/dontrun.ps1 new file mode 100644 index 0000000000..bb6d576e6a --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/dontrun.ps1 @@ -0,0 +1 @@ +Throw "This script should not be run" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-beforemodify-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-install-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-uninstall-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/post-upgrade-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-beforemodify-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-doesnotexist.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-doesnotexist.ps1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-install-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-uninstall-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/hook/pre-upgrade-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/scriptpackage.hook.nuspec b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/scriptpackage.hook.nuspec new file mode 100644 index 0000000000..dbad56120b --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/1.0.0/scriptpackage.hook.nuspec @@ -0,0 +1,16 @@ + + + + + scriptpackage.hook + 1.0.0 + scriptpackage.hook + TheCakeIsNaOH + scriptpackage.hook + Package with various hook scripts + Package with various hook scripts. Intended to test + + + + + diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/dontrun.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/dontrun.ps1 new file mode 100644 index 0000000000..bb6d576e6a --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/dontrun.ps1 @@ -0,0 +1 @@ +Throw "This script should not be run" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-beforemodify-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-doesnotexist.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-doesnotexist.ps1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-install-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-uninstall-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/post-upgrade-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-beforemodify-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-install-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-uninstall-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-all.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-all.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-all.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-installpackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-installpackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-installpackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-upgradepackage.ps1 b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-upgradepackage.ps1 new file mode 100644 index 0000000000..1e44654cbe --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/hook/pre-upgrade-upgradepackage.ps1 @@ -0,0 +1 @@ +Write-Output "$($MyInvocation.MyCommand.Name) hook ran for $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/scriptpackage.hook.nuspec b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/scriptpackage.hook.nuspec new file mode 100644 index 0000000000..10cc1807d3 --- /dev/null +++ b/src/chocolatey.tests.integration/context/scriptpackage.hook/2.0.0/scriptpackage.hook.nuspec @@ -0,0 +1,16 @@ + + + + + scriptpackage.hook + 2.0.0 + scriptpackage.hook + TheCakeIsNaOH + scriptpackage.hook + Package with various hook scripts + Package with various hook scripts. Intended to test + + + + + diff --git a/src/chocolatey.tests.integration/packages.config b/src/chocolatey.tests.integration/packages.config index cbce9b63e2..1088cda952 100644 --- a/src/chocolatey.tests.integration/packages.config +++ b/src/chocolatey.tests.integration/packages.config @@ -1,5 +1,4 @@  - @@ -10,5 +9,5 @@ - + \ No newline at end of file diff --git a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs index 9b795ab92b..4d14d58ef5 100644 --- a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs @@ -225,6 +225,14 @@ public void should_not_create_an_extensions_folder_for_the_package() Directory.Exists(extensionsDirectory).ShouldBeFalse(); } + [Fact] + public void should_not_create_an_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames); + + Directory.Exists(hooksDirectory).ShouldBeFalse(); + } + [Fact] [WindowsOnly] [Platform(Exclude = "Mono")] @@ -1408,6 +1416,22 @@ public void should_contain_a_warning_message_that_it_installed_successfully() installedSuccessfully.ShouldBeTrue(); } + [Fact] + public void should_contain_a_warning_message_that_installing_package_with_multiple_versions_being_deprecated() + { + const string expected = "Installing the same package with multiple versions is deprecated and will be removed in v2.0.0."; + + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains(expected)) + { + return; + } + } + + Assert.Fail("No warning message about side by side deprecation outputted"); + } + [Fact] public void should_have_a_successful_package_result() { @@ -3178,6 +3202,14 @@ public void should_not_create_an_extensions_folder_for_the_package() Directory.Exists(extensionsDirectory).ShouldBeFalse(); } + [Fact] + public void should_not_create_an_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", "installpackage"); + + Directory.Exists(hooksDirectory).ShouldBeFalse(); + } + [Fact] [WindowsOnly] [Platform(Exclude = "Mono")] @@ -3386,5 +3418,600 @@ public void should_not_install_any_packages() Results.Count().ShouldEqual(0); } } + + public class when_installing_a_hook_package : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "scriptpackage.hook"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + ".1.0.0" + Constants.PackageExtension); + } + + private PackageResult _packageResult; + + public override void Because() + { + Results = Service.install_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_install_the_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_expected_version_of_the_package() + { + var packageFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, Configuration.PackageNames + Constants.PackageExtension); + var package = new OptimizedZipPackage(packageFile); + package.Version.Version.to_string().ShouldEqual("1.0.0.0"); + } + + [Fact] + public void should_not_create_an_extensions_folder_for_the_package() + { + var extensionsDirectory = Path.Combine(Scenario.get_top_level(), "extensions", Configuration.PackageNames); + + Directory.Exists(extensionsDirectory).ShouldBeFalse(); + } + + [Fact] + public void should_create_a_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty)); + + Directory.Exists(hooksDirectory).ShouldBeTrue(); + } + + [Fact] + public void should_install_hook_scripts_to_folder() + { + var hookScripts = new List { "pre-install-all.ps1", "post-install-all.ps1", "pre-upgrade-all.ps1", "post-upgrade-all.ps1", "pre-uninstall-all.ps1", "post-uninstall-all.ps1" }; + foreach (string scriptName in hookScripts) + { + var hookScriptPath = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty), scriptName); + File.ReadAllText(hookScriptPath).ShouldContain("Write-Output"); + } + } + + [Fact] + public void should_contain_a_warning_message_that_it_installed_successfully() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("1/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + public void should_have_a_version_of_one_dot_zero_dot_zero() + { + _packageResult.Version.ShouldEqual("1.0.0"); + } + + } + + public class when_installing_a_package_happy_path_with_hook_scripts : ScenariosBase + { + private PackageResult _packageResult; + + public override void Context() + { + base.Context(); + Scenario.add_packages_to_source_location(Configuration, "scriptpackage.hook" + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "scriptpackage.hook", "1.0.0"); + Configuration.PackageNames = Configuration.Input = "installpackage"; + } + + public override void Because() + { + Results = Service.install_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_install_where_install_location_reports() + { + Directory.Exists(_packageResult.InstallLocation).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_expected_version_of_the_package() + { + var packageFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, Configuration.PackageNames + Constants.PackageExtension); + var package = new OptimizedZipPackage(packageFile); + package.Version.Version.to_string().ShouldEqual("1.0.0.0"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_create_a_shim_for_console_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "console.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_create_a_shim_for_graphical_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "graphical.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + public void should_not_create_a_shim_for_ignored_executable_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "not.installed.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_a_shim_for_mismatched_case_ignored_executable_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "casemismatch.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_an_extensions_folder_for_the_package() + { + var extensionsDirectory = Path.Combine(Scenario.get_top_level(), "extensions", Configuration.PackageNames); + + Directory.Exists(extensionsDirectory).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_an_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames); + + Directory.Exists(hooksDirectory).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_a_console_shim_that_is_set_for_non_gui_access() + { + var messages = new List(); + + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "console.exe"); + CommandExecutor.execute( + shimfile, + "--shimgen-noop", + 10, + stdOutAction: (s, e) => messages.Add(e.Data), + stdErrAction: (s, e) => messages.Add(e.Data) + ); + + var messageFound = false; + + foreach (var message in messages.or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(message)) continue; + if (message.Contains("is gui? False")) messageFound = true; + } + + messageFound.ShouldBeTrue("GUI false message not found"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_a_graphical_shim_that_is_set_for_gui_access() + { + var messages = new List(); + + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "graphical.exe"); + CommandExecutor.execute( + shimfile, + "--shimgen-noop", + 10, + stdOutAction: (s, e) => messages.Add(e.Data), + stdErrAction: (s, e) => messages.Add(e.Data) + ); + + var messageFound = false; + + foreach (var message in messages.or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(message)) continue; + if (message.Contains("is gui? True")) messageFound = true; + } + + messageFound.ShouldBeTrue("GUI true message not found"); + } + + [Fact] + public void should_contain_a_warning_message_that_it_installed_successfully() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("1/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + public void should_have_a_version_of_one_dot_zero_dot_zero() + { + _packageResult.Version.ShouldEqual("1.0.0"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_chocolateyInstall_script() + { + MockLogger.contains_message("installpackage v1.0.0 has been installed", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_all_hook_script() + { + MockLogger.contains_message("pre-install-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_all_hook_script() + { + MockLogger.contains_message("post-install-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_installpackage_hook_script() + { + MockLogger.contains_message("pre-install-installpackage.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_installpackage_hook_script() + { + MockLogger.contains_message("post-install-installpackage.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_uninstall_hook_script() + { + MockLogger.contains_message("post-uninstall-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_upgradepackage_hook_script() + { + MockLogger.contains_message("pre-install-upgradepackage.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_beforemodify_hook_script() + { + MockLogger.contains_message("pre-beforemodify-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + } + + public class when_installing_a_portable_package_happy_path_with_hook_scripts : ScenariosBase + { + private PackageResult _packageResult; + + public override void Context() + { + base.Context(); + Scenario.add_packages_to_source_location(Configuration, "scriptpackage.hook" + ".1.0.0" + Constants.PackageExtension); + Scenario.install_package(Configuration, "scriptpackage.hook", "1.0.0"); + Configuration.PackageNames = Configuration.Input = "portablepackage"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + } + + public override void Because() + { + Results = Service.install_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_install_where_install_location_reports() + { + Directory.Exists(_packageResult.InstallLocation).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_expected_version_of_the_package() + { + var packageFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, Configuration.PackageNames + Constants.PackageExtension); + var package = new OptimizedZipPackage(packageFile); + package.Version.Version.to_string().ShouldEqual("1.0.0.0"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_create_a_shim_for_console_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "console.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_create_a_shim_for_graphical_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "graphical.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + public void should_not_create_a_shim_for_ignored_executable_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "not.installed.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_a_shim_for_mismatched_case_ignored_executable_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "casemismatch.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_an_extensions_folder_for_the_package() + { + var extensionsDirectory = Path.Combine(Scenario.get_top_level(), "extensions", Configuration.PackageNames); + + Directory.Exists(extensionsDirectory).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_an_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames); + + Directory.Exists(hooksDirectory).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_a_console_shim_that_is_set_for_non_gui_access() + { + var messages = new List(); + + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "console.exe"); + CommandExecutor.execute( + shimfile, + "--shimgen-noop", + 10, + stdOutAction: (s, e) => messages.Add(e.Data), + stdErrAction: (s, e) => messages.Add(e.Data) + ); + + var messageFound = false; + + foreach (var message in messages.or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(message)) continue; + if (message.Contains("is gui? False")) messageFound = true; + } + + messageFound.ShouldBeTrue("GUI false message not found"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_a_graphical_shim_that_is_set_for_gui_access() + { + var messages = new List(); + + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "graphical.exe"); + CommandExecutor.execute( + shimfile, + "--shimgen-noop", + 10, + stdOutAction: (s, e) => messages.Add(e.Data), + stdErrAction: (s, e) => messages.Add(e.Data) + ); + + var messageFound = false; + + foreach (var message in messages.or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(message)) continue; + if (message.Contains("is gui? True")) messageFound = true; + } + + messageFound.ShouldBeTrue("GUI true message not found"); + } + + [Fact] + public void should_contain_a_warning_message_that_it_installed_successfully() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("1/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + public void should_have_a_version_of_one_dot_zero_dot_zero() + { + _packageResult.Version.ShouldEqual("1.0.0"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_chocolateyInstall_script() + { + MockLogger.contains_message("portablepackage v1.0.0 has been installed", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_all_hook_script() + { + MockLogger.contains_message("pre-install-all.ps1 hook ran for portablepackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_all_hook_script() + { + MockLogger.contains_message("post-install-all.ps1 hook ran for portablepackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_uninstall_hook_script() + { + MockLogger.contains_message("post-uninstall-all.ps1 hook ran for portablepackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_upgradepackage_hook_script() + { + MockLogger.contains_message("pre-install-upgradepackage.ps1 hook ran for portablepackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_beforemodify_hook_script() + { + MockLogger.contains_message("pre-beforemodify-all.ps1 hook ran for portablepackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + } } } diff --git a/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs index 850c52b78f..cfc4b07868 100644 --- a/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs @@ -1065,5 +1065,256 @@ public void should_have_expected_error_in_package_result() errorFound.ShouldBeTrue(); } } + + + public class when_uninstalling_a_hook_package : ScenariosBase + { + private PackageResult _packageResult; + + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "scriptpackage.hook"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + ".1.0.0" + Constants.PackageExtension); + Service.install_run(Configuration); + } + + public override void Because() + { + Results = Service.uninstall_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_remove_the_package_from_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + public void should_delete_the_rollback() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib-bkp", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + public void should_contain_a_warning_message_that_it_uninstalled_successfully() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("1/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + public void should_remove_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty)); + + Directory.Exists(hooksDirectory).ShouldBeFalse(); + } + } + + public class when_uninstalling_a_package_happy_path_with_hooks : ScenariosBase + { + private PackageResult _packageResult; + + public override void Context() + { + base.Context(); + Scenario.add_packages_to_source_location(Configuration, "scriptpackage.hook" + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "scriptpackage.hook", "1.0.0"); + } + + public override void Because() + { + Results = Service.uninstall_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_remove_the_package_from_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + public void should_delete_the_rollback() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib-bkp", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_delete_a_shim_for_console_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "console.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_delete_a_shim_for_graphical_in_the_bin_directory() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "graphical.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_delete_any_files_created_during_the_install() + { + var generatedFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, "simplefile.txt"); + + File.Exists(generatedFile).ShouldBeFalse(); + } + + [Fact] + public void should_contain_a_warning_message_that_it_uninstalled_successfully() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("1/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_chocolateyBeforeModify_script() + { + MockLogger.contains_message("installpackage 1.0.0 Before Modification", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_chocolateyUninstall_script() + { + MockLogger.contains_message("installpackage 1.0.0 Uninstalled", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_all_hook_script() + { + MockLogger.contains_message("pre-uninstall-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_all_hook_script() + { + MockLogger.contains_message("post-uninstall-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_installpackage_hook_script() + { + MockLogger.contains_message("pre-uninstall-installpackage.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_installpackage_hook_script() + { + MockLogger.contains_message("post-uninstall-installpackage.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_upgradepackage_hook_script() + { + MockLogger.contains_message("pre-uninstall-upgradepackage.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_beforemodify_hook_script() + { + MockLogger.contains_message("pre-beforemodify-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_beforemodify_hook_script() + { + MockLogger.contains_message("post-beforemodify-all.ps1 hook ran for installpackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + } + } } diff --git a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs index 8b607110d0..b3bdc20c7d 100644 --- a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs @@ -18,6 +18,7 @@ namespace chocolatey.tests.integration.scenarios { using System; using System.Collections.Concurrent; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -3173,5 +3174,356 @@ public void should_skip_packages_in_except_list() upgradePackageResult.Count.ShouldEqual(0, "upgradepackage should not be in the results list"); } } + + public class when_upgrading_an_existing_hook_package : ScenariosBase + { + private PackageResult _packageResult; + + public override void Context() + { + base.Context(); + Scenario.add_packages_to_source_location(Configuration, "scriptpackage.hook" + ".1.0.0" + Constants.PackageExtension); + Scenario.install_package(Configuration, "scriptpackage.hook", "1.0.0"); + Configuration.PackageNames = Configuration.Input = "scriptpackage.hook"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + ".2.0.0" + Constants.PackageExtension); + } + + public override void Because() + { + Results = Service.upgrade_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_upgrade_where_install_location_reports() + { + Directory.Exists(_packageResult.InstallLocation).ShouldBeTrue(); + } + + [Fact] + public void should_upgrade_a_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_delete_the_rollback() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib-bkp", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + public void should_upgrade_the_package() + { + var packageFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, Configuration.PackageNames + Constants.PackageExtension); + var package = new OptimizedZipPackage(packageFile); + package.Version.Version.to_string().ShouldEqual("2.0.0.0"); + } + + [Fact] + public void should_contain_a_warning_message_that_it_upgraded_successfully() + { + bool upgradedSuccessMessage = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("upgraded 1/1")) upgradedSuccessMessage = true; + } + + upgradedSuccessMessage.ShouldBeTrue(); + } + + [Fact] + public void should_contain_a_warning_message_with_old_and_new_versions() + { + bool upgradeMessage = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("You have scriptpackage.hook v1.0.0 installed. Version 2.0.0 is available based on your source")) upgradeMessage = true; + } + + upgradeMessage.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + public void should_match_the_upgrade_version_of_two_dot_zero_dot_zero() + { + _packageResult.Version.ShouldEqual("2.0.0"); + } + + [Fact] + public void should_have_a_hooks_folder_for_the_package() + { + var hooksDirectory = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty)); + + Directory.Exists(hooksDirectory).ShouldBeTrue(); + } + + [Fact] + public void should_install_hook_scripts_to_folder() + { + var hookScripts = new List { "pre-install-all.ps1", "post-install-all.ps1", "pre-upgrade-all.ps1", "post-upgrade-all.ps1", "pre-uninstall-all.ps1", "post-uninstall-all.ps1" }; + foreach (string scriptName in hookScripts) + { + var hookScriptPath = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty), scriptName); + File.ReadAllText(hookScriptPath).ShouldContain("Write-Output"); + } + } + + [Fact] + public void should_remove_files_not_in_upgrade_version() + { + var hookScriptPath = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty), "pre-install-doesnotexist.ps1"); + File.Exists(hookScriptPath).ShouldBeFalse(); + } + + [Fact] + public void should_install_new_files_in_upgrade_version() + { + var hookScriptPath = Path.Combine(Scenario.get_top_level(), "hooks", Configuration.PackageNames.Replace(".hook", string.Empty), "post-install-doesnotexist.ps1"); + File.Exists(hookScriptPath).ShouldBeTrue(); + } + } + + public class when_upgrading_an_existing_package_happy_path_with_hooks : ScenariosBase + { + private PackageResult _packageResult; + + public override void Context() + { + base.Context(); + Scenario.add_packages_to_source_location(Configuration, "scriptpackage.hook" + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "scriptpackage.hook", "1.0.0"); + Configuration.PackageNames = Configuration.Input = "upgradepackage"; + } + + public override void Because() + { + Results = Service.upgrade_run(Configuration); + _packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_upgrade_where_install_location_reports() + { + Directory.Exists(_packageResult.InstallLocation).ShouldBeTrue(); + } + + [Fact] + public void should_upgrade_a_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_delete_the_rollback() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib-bkp", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + public void should_contain_newer_version_in_directory() + { + var shimFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, "tools", "console.exe"); + + File.ReadAllText(shimFile).ShouldEqual("1.1.0"); + } + + [Fact] + public void should_upgrade_the_package() + { + var packageFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames, Configuration.PackageNames + Constants.PackageExtension); + var package = new OptimizedZipPackage(packageFile); + package.Version.Version.to_string().ShouldEqual("1.1.0.0"); + } + + [Fact] + public void should_contain_a_warning_message_that_it_upgraded_successfully() + { + bool upgradedSuccessMessage = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("upgraded 1/1")) upgradedSuccessMessage = true; + } + + upgradedSuccessMessage.ShouldBeTrue(); + } + + [Fact] + public void should_contain_a_warning_message_with_old_and_new_versions() + { + bool upgradeMessage = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("You have upgradepackage v1.0.0 installed. Version 1.1.0 is available based on your source")) upgradeMessage = true; + } + + upgradeMessage.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + _packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + _packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + _packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + _packageResult.Name.ShouldEqual(Configuration.PackageNames); + } + + [Fact] + public void should_match_the_upgrade_version_of_one_dot_one_dot_zero() + { + _packageResult.Version.ShouldEqual("1.1.0"); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_chocolateyBeforeModify_script_for_original_package() + { + MockLogger.contains_message("upgradepackage 1.0.0 Before Modification", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_chocolateyBeforeModify_before_chocolateyInstall() + { + MockLogger.MessagesFor(LogLevel.Info).or_empty_list_if_null() + .SkipWhile(p => !p.Contains("upgradepackage 1.0.0 Before Modification")) + .Any(p => p.EndsWith("upgradepackage 1.1.0 Installed")) + .ShouldBeTrue(); + } + + [Fact] + public void should_not_have_executed_chocolateyUninstall_script_for_original_package() + { + MockLogger.contains_message("upgradepackage 1.0.0 Uninstalled", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + public void should_not_have_executed_chocolateyBeforeModify_script_for_new_package() + { + MockLogger.contains_message("upgradepackage 1.1.0 Before Modification", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_chocolateyInstall_script_for_new_package() + { + MockLogger.contains_message("upgradepackage 1.1.0 Installed", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_all_hook_script() + { + MockLogger.contains_message("pre-install-all.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_all_hook_script() + { + MockLogger.contains_message("post-install-all.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_pre_upgradepackage_hook_script() + { + MockLogger.contains_message("pre-install-upgradepackage.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_post_upgradepackage_hook_script() + { + MockLogger.contains_message("post-install-upgradepackage.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_uninstall_hook_script() + { + MockLogger.contains_message("post-uninstall-all.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_installpackage_hook_script() + { + MockLogger.contains_message("pre-install-installpackage.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeFalse(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_have_executed_beforemodify_hook_script_for_previous_version() + { + MockLogger.contains_message("pre-beforemodify-all.ps1 hook ran for upgradepackage 1.0.0", LogLevel.Info).ShouldBeTrue(); + } + + [Fact] + [WindowsOnly] + [Platform(Exclude = "Mono")] + public void should_not_have_executed_beforemodify_hook_script_for_upgrade_version() + { + MockLogger.contains_message("pre-beforemodify-all.ps1 hook ran for upgradepackage 1.1.0", LogLevel.Info).ShouldBeFalse(); + } + } } } diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 10e34886c4..97654a7838 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -70,11 +70,8 @@ ..\packages\Should.1.1.12.0\lib\Should.dll - - ..\packages\SimpleInjector.2.5.0\lib\net40-client\SimpleInjector.dll - - - ..\packages\SimpleInjector.2.5.0\lib\net40-client\SimpleInjector.Diagnostics.dll + + ..\packages\SimpleInjector.2.8.3\lib\net40-client\SimpleInjector.dll diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs index cf45b7efd6..9f93bf3d55 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs @@ -235,6 +235,36 @@ public void should_add_short_version_of_password_to_the_option_set() { optionSet.Contains("p").ShouldBeTrue(); } + + [Fact] + public void should_add_pin_to_the_option_set() + { + optionSet.Contains("pinpackage").ShouldBeTrue(); + } + + [Fact] + public void should_add_long_version_of_pin_to_the_option_set() + { + optionSet.Contains("pin-package").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_pin_to_the_option_set() + { + optionSet.Contains("pin").ShouldBeTrue(); + } + + [Fact] + public void should_add_skip_hooks_to_the_option_set() + { + optionSet.Contains("skip-hooks").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_skip_hooks_to_the_option_set() + { + optionSet.Contains("skiphooks").ShouldBeTrue(); + } } public class when_handling_additional_argument_parsing : ChocolateyInstallCommandSpecsBase diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUninstallCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUninstallCommandSpecs.cs index 8a3433c2c3..ecba458585 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUninstallCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUninstallCommandSpecs.cs @@ -169,6 +169,18 @@ public void should_add_short_version_of_skippowershell_to_the_option_set() { optionSet.Contains("n").ShouldBeTrue(); } + + [Fact] + public void should_add_skip_hooks_to_the_option_set() + { + optionSet.Contains("skip-hooks").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_skip_hooks_to_the_option_set() + { + optionSet.Contains("skiphooks").ShouldBeTrue(); + } } public class when_handling_additional_argument_parsing : ChocolateyUninstallCommandSpecsBase diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs index 2b179381e6..50ba3eccf8 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs @@ -217,6 +217,36 @@ public void should_add_short_version_of_password_to_the_option_set() { optionSet.Contains("p").ShouldBeTrue(); } + + [Fact] + public void should_add_pin_to_the_option_set() + { + optionSet.Contains("pinpackage").ShouldBeTrue(); + } + + [Fact] + public void should_add_long_version_of_pin_to_the_option_set() + { + optionSet.Contains("pin-package").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_pin_to_the_option_set() + { + optionSet.Contains("pin").ShouldBeTrue(); + } + + [Fact] + public void should_add_skip_hooks_to_the_option_set() + { + optionSet.Contains("skip-hooks").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_skip_hooks_to_the_option_set() + { + optionSet.Contains("skiphooks").ShouldBeTrue(); + } } public class when_handling_additional_argument_parsing : ChocolateyUpgradeCommandSpecsBase diff --git a/src/chocolatey.tests/infrastructure.app/services/ChocolateyPackageServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/ChocolateyPackageServiceSpecs.cs index 32a0c45f25..43317e0198 100644 --- a/src/chocolatey.tests/infrastructure.app/services/ChocolateyPackageServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/ChocolateyPackageServiceSpecs.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,8 +81,8 @@ public override void Context() Configuration.PackageNames = @"C:\test\packages.config"; Configuration.Sources = @"C:\test"; - NormalRunner.Setup(r => r.SourceType).Returns(SourceType.normal); - FeaturesRunner.Setup(r => r.SourceType).Returns(SourceType.windowsfeatures); + NormalRunner.Setup(r => r.SourceType).Returns(SourceTypes.NORMAL); + FeaturesRunner.Setup(r => r.SourceType).Returns(SourceTypes.WINDOWS_FEATURES); var package = new Mock(); var expectedResult = new ConcurrentDictionary(); @@ -92,7 +92,7 @@ public override void Context() .Returns(expectedResult); NormalRunner.Setup(r => r.install_run(It.IsAny(), It.IsAny>())) .Returns(new ConcurrentDictionary()); - SourceRunners.AddRange(new []{ NormalRunner.Object, FeaturesRunner.Object }); + SourceRunners.AddRange(new[] { NormalRunner.Object, FeaturesRunner.Object }); FileSystem.Setup(f => f.get_full_path(Configuration.PackageNames)).Returns(Configuration.PackageNames); FileSystem.Setup(f => f.file_exists(Configuration.PackageNames)).Returns(true); diff --git a/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs index 48b3f2ef71..82db549daa 100644 --- a/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/TemplateServiceSpecs.cs @@ -26,6 +26,7 @@ namespace chocolatey.tests.infrastructure.app.services using chocolatey.infrastructure.app.services; using chocolatey.infrastructure.app.templates; using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.services; using Moq; using NUnit.Framework; using Should; @@ -36,12 +37,14 @@ public abstract class TemplateServiceSpecsBase : TinySpec { protected TemplateService service; protected Mock fileSystem = new Mock(); + protected Mock xmlService = new Mock(); public override void Context() { fileSystem.ResetCalls(); + xmlService.ResetCalls(); - service = new TemplateService(fileSystem.Object); + service = new TemplateService(fileSystem.Object, xmlService.Object); } } diff --git a/src/chocolatey.tests/packages.config b/src/chocolatey.tests/packages.config index bb0816bde7..81ae8aa55c 100644 --- a/src/chocolatey.tests/packages.config +++ b/src/chocolatey.tests/packages.config @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/src/chocolatey/AssemblyExtensions.cs b/src/chocolatey/AssemblyExtensions.cs index 24cbe0624c..4f37aedb6b 100644 --- a/src/chocolatey/AssemblyExtensions.cs +++ b/src/chocolatey/AssemblyExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,9 +16,13 @@ namespace chocolatey { + using System; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; + using chocolatey.infrastructure.app.registration; + using chocolatey.infrastructure.logging; using infrastructure.adapters; /// @@ -86,5 +90,53 @@ public static string get_public_key_token(this AssemblyName assemblyName) return publicKeyToken.Select(x => x.ToString("x2")).Aggregate((x, y) => x + y); } + + public static IEnumerable get_loadable_types(this IAssembly assembly) + { + // Code originates from the following stack overflow answer: https://stackoverflow.com/a/11915414 + if (assembly == null) + { + throw new ArgumentNullException("assembly"); + } + + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types.Where(t => t != null); + } + } + + public static IEnumerable get_extension_modules(this IAssembly assembly) + { + var result = new List(); + + "chocolatey".Log().Debug("Gathering exported extension registration modules!"); + + var registrationTypes = assembly + .get_loadable_types() + .Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && typeof(IExtensionModule).IsAssignableFrom(t)); + + foreach (var extensionType in registrationTypes) + { + try + { + var module = (IExtensionModule)Activator.CreateInstance(extensionType); + result.Add(module); + } + catch (Exception ex) + { + "chocolatey".Log().Error("Unable to activate extension module '{0}' in assembly '{1}'.\n Message:{2}", + extensionType.Name, + assembly.GetName().Name, + ex.Message); + "chocolatey".Log().Error(ChocolateyLoggers.LogFileOnly, ex.StackTrace); + } + } + + return result; + } } } diff --git a/src/chocolatey/FileSystemExtensions.cs b/src/chocolatey/FileSystemExtensions.cs new file mode 100644 index 0000000000..7c3dcf0bea --- /dev/null +++ b/src/chocolatey/FileSystemExtensions.cs @@ -0,0 +1,69 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using chocolatey.infrastructure.adapters; + using chocolatey.infrastructure.app; + using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.logging; + using chocolatey.infrastructure.registration; + + public static class FileSystemExtensions + { + internal static IEnumerable get_extension_assemblies(this IFileSystem fileSystem) + { + var result = new List(); + + if (!fileSystem.directory_exists(ApplicationParameters.ExtensionsLocation)) + { + return result; + } + + var extensionDllFiles = fileSystem.get_files(ApplicationParameters.ExtensionsLocation, "*.dll", SearchOption.AllDirectories); + + foreach (var extensionFile in extensionDllFiles) + { + var name = fileSystem.get_file_name_without_extension(extensionFile); + + try + { + var assembly = AssemblyResolution.load_extension(name); + + if (assembly == null) + { + "chocolatey".Log().Warn("Unable to load extension from path {0}.\n The assembly is not signed with official key token.", extensionFile); + } + else + { + result.Add(assembly); + } + } + catch (Exception ex) + { + "chocolatey".Log().Error("Unable to load extension from path {0}.\n Message:{1}", extensionFile, ex.Message); + "chocolatey".Log().Error(ChocolateyLoggers.LogFileOnly, ex.StackTrace); + } + } + + return result.Distinct(); + } + } +} diff --git a/src/chocolatey/GetChocolatey.cs b/src/chocolatey/GetChocolatey.cs index 9dc45d49dd..c86309c094 100644 --- a/src/chocolatey/GetChocolatey.cs +++ b/src/chocolatey/GetChocolatey.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,9 +32,13 @@ namespace chocolatey using infrastructure.registration; using infrastructure.synchronization; using log4net; + #if !NoResources + using resources; + #endif + using Assembly = infrastructure.adapters.Assembly; using IFileSystem = infrastructure.filesystem.IFileSystem; using ILog = infrastructure.logging.ILog; @@ -66,19 +70,53 @@ public static GetChocolatey GetChocolatey(bool initializeLogging) } private static ResolveEventHandler _handler = null; + private static void add_assembly_resolver() { _handler = (sender, args) => { var requestedAssembly = new AssemblyName(args.Name); - // There are things that are ILMerged into Chocolatey. Anything with - // the right public key except licensed should use the choco/chocolatey assembly #if FORCE_CHOCOLATEY_OFFICIAL_KEY var chocolateyPublicKey = ApplicationParameters.OfficialChocolateyPublicKey; #else var chocolateyPublicKey = ApplicationParameters.UnofficialChocolateyPublicKey; #endif + + if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyPublicKey)) + { + // Check if it is already loaded + var resolvedAssembly = AssemblyResolution.resolve_existing_assembly(requestedAssembly.Name, chocolateyPublicKey); + + if (resolvedAssembly != null) + { + return resolvedAssembly.UnderlyingType; + } + + if (Directory.Exists(ApplicationParameters.ExtensionsLocation)) + { + foreach (var extensionDll in Directory.EnumerateFiles(ApplicationParameters.ExtensionsLocation, requestedAssembly.Name + ".dll", SearchOption.AllDirectories)) + { + try + { + resolvedAssembly = AssemblyResolution.load_assembly(requestedAssembly.Name, extensionDll, chocolateyPublicKey); + + if (resolvedAssembly != null) + { + return resolvedAssembly.UnderlyingType; + } + } + catch (Exception ex) + { + // This catch statement is empty on purpose, we do + // not want to do anything if it fails to load. + } + } + } + } + + // There are things that are ILMerged into Chocolatey. Anything with + // the right public key except extensions should use the choco/chocolatey assembly if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyPublicKey) && !requestedAssembly.Name.is_equal_to(ApplicationParameters.LicensedChocolateyAssemblySimpleName) && !requestedAssembly.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index ea349a4a75..c59d36b1e9 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -23,6 +23,7 @@ DEBUG;TRACE prompt 4 + bin\Debug\chocolatey.xml pdbonly @@ -81,8 +82,8 @@ ..\..\lib\Rhino.Licensing.1.4.1\lib\net40\Rhino.Licensing.dll - - ..\packages\SimpleInjector.2.5.0\lib\net40-client\SimpleInjector.dll + + ..\packages\SimpleInjector.2.8.3\lib\net40-client\SimpleInjector.dll @@ -113,13 +114,23 @@ Properties\SolutionVersion.cs + + + + + + + + + + @@ -149,6 +160,8 @@ + + diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 982fb6d1bd..0b215850db 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -88,6 +88,8 @@ public static class ApplicationParameters public static readonly string ChocolateyPackageInfoStoreLocation = _fileSystem.combine_paths(InstallLocation, ".chocolatey"); public static readonly string ExtensionsLocation = _fileSystem.combine_paths(InstallLocation, "extensions"); public static readonly string TemplatesLocation = _fileSystem.combine_paths(InstallLocation, "templates"); + public static readonly string HooksLocation = _fileSystem.combine_paths(InstallLocation, "hooks"); + public static readonly string HookPackageIdExtension = ".hook"; public static readonly string ChocolateyCommunityFeedPushSourceOld = "https://chocolatey.org/"; public static readonly string ChocolateyCommunityFeedPushSource = "https://push.chocolatey.org/"; public static readonly string ChocolateyCommunityGalleryUrl = "https://community.chocolatey.org/"; diff --git a/src/chocolatey/infrastructure.app/attributes/MultiServiceAttribute.cs b/src/chocolatey/infrastructure.app/attributes/MultiServiceAttribute.cs new file mode 100644 index 0000000000..2745bbb9da --- /dev/null +++ b/src/chocolatey/infrastructure.app/attributes/MultiServiceAttribute.cs @@ -0,0 +1,37 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.attributes +{ + using System; + + [AttributeUsage(AttributeTargets.Interface, Inherited = true, AllowMultiple = false)] + internal sealed class MultiServiceAttribute : Attribute + { + public bool IsMultiService { get; private set; } + + // This is a positional argument + public MultiServiceAttribute() + : this(isMultiService: true) + { + } + + public MultiServiceAttribute(bool isMultiService) + { + IsMultiService = isMultiService; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index eaeda919fb..39877f5c57 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,14 +23,12 @@ namespace chocolatey.infrastructure.app.builders using System.Reflection; using System.Text; using adapters; - using attributes; using chocolatey.infrastructure.app.commands; using configuration; using cryptography; using extractors; using filesystem; using information; - using infrastructure.commands; using infrastructure.services; using licensing; using logging; @@ -60,6 +58,15 @@ private static IEnvironment Environment get { return _environmentInitializer.Value; } } + public static bool is_compatibility_checks_disabled(IFileSystem filesystem, IXmlService xmlService) + { + var config = get_config_file_settings(filesystem, xmlService); + + var feature = config.Features.FirstOrDefault(f => f.Name.is_equal_to("disableCompatibilityChecks")); + + return feature != null && feature.Enabled; + } + /// /// Sets up the configuration based on arguments passed in, config file, and environment /// @@ -105,7 +112,7 @@ private static void set_config_file_settings(ConfigFileSettings configFileSettin () => xmlService.serialize(configFileSettings, globalConfigPath, isSilent: shouldLogSilently), "Error updating '{0}'. Please ensure you have permissions to do so".format_with(globalConfigPath), logDebugInsteadOfError: true, - isSilent:shouldLogSilently); + isSilent: shouldLogSilently); } private static void add_or_remove_licensed_source(ChocolateyLicense license, ConfigFileSettings configFileSettings) @@ -191,30 +198,32 @@ private static void set_machine_sources(ChocolateyConfiguration config, ConfigFi foreach (var source in defaultSourcesInOrder) { config.MachineSources.Add(new MachineSourceConfiguration - { - Key = source.Value, - Name = source.Id, - Username = source.UserName, - EncryptedPassword = source.Password, - Certificate = source.Certificate, - EncryptedCertificatePassword = source.CertificatePassword, - Priority = source.Priority, - BypassProxy = source.BypassProxy, - AllowSelfService = source.AllowSelfService, - VisibleToAdminsOnly = source.VisibleToAdminsOnly - }); + { + Key = source.Value, + Name = source.Id, + Username = source.UserName, + EncryptedPassword = source.Password, + Certificate = source.Certificate, + EncryptedCertificatePassword = source.CertificatePassword, + Priority = source.Priority, + BypassProxy = source.BypassProxy, + AllowSelfService = source.AllowSelfService, + VisibleToAdminsOnly = source.VisibleToAdminsOnly + }); } } private static void set_config_items(ChocolateyConfiguration config, ConfigFileSettings configFileSettings, IFileSystem fileSystem) { config.CacheLocation = Environment.ExpandEnvironmentVariables(set_config_item(ApplicationParameters.ConfigSettings.CacheLocation, configFileSettings, string.IsNullOrWhiteSpace(configFileSettings.CacheLocation) ? string.Empty : configFileSettings.CacheLocation, "Cache location if not TEMP folder. Replaces `$env:TEMP` value for choco.exe process. It is highly recommended this be set to make Chocolatey more deterministic in cleanup.")); - if (string.IsNullOrWhiteSpace(config.CacheLocation)) { + if (string.IsNullOrWhiteSpace(config.CacheLocation)) + { config.CacheLocation = fileSystem.get_temp_path(); // System.Environment.GetEnvironmentVariable("TEMP"); // TEMP gets set in EnvironmentSettings, so it may already have // chocolatey in the path when it installs the next package from // the API. - if(!String.Equals(fileSystem.get_directory_info_for(config.CacheLocation).Name, "chocolatey", StringComparison.OrdinalIgnoreCase)) { + if (!string.Equals(fileSystem.get_directory_info_for(config.CacheLocation).Name, "chocolatey", StringComparison.OrdinalIgnoreCase)) + { config.CacheLocation = fileSystem.combine_paths(fileSystem.get_temp_path(), "chocolatey"); } } @@ -423,7 +432,7 @@ private static void set_global_options(IList args, ChocolateyConfigurati option => config.Proxy.BypassOnLocal = option != null) .Add("log-file=", "Log File to output to in addition to regular loggers. Available in 0.10.8+.", - option => config.AdditionalLogFileLocation= option.remove_surrounding_quotes()) + option => config.AdditionalLogFileLocation = option.remove_surrounding_quotes()) .Add("skipcompatibilitychecks|skip-compatibility-checks", "SkipCompatibilityChecks - Prevent warnings being shown before and after command execution when a runtime compatibility problem is found between the version of Chocolatey and the Chocolatey Licensed Extension. Available in 1.1.0+", option => config.DisableCompatibilityChecks = option != null) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyHelpCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyHelpCommand.cs index 0b2342ea2b..229330cac4 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyHelpCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyHelpCommand.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -6,7 +6,7 @@ // // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.commands { + using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -83,13 +84,12 @@ public static void display_help_message(Container container = null) IEnumerable commands = container.GetAllInstances(); - foreach (var command in commands.or_empty_list_if_null()) + foreach (var command in commands.or_empty_list_if_null().SelectMany(c => { - var attributes = command.GetType().GetCustomAttributes(typeof(CommandForAttribute), false).Cast(); - foreach (var attribute in attributes.or_empty_list_if_null()) - { - commandsLog.AppendFormat(" * {0} - {1}\n", attribute.CommandName, attribute.Description); - } + return c.GetType().GetCustomAttributes(typeof(CommandForAttribute), false).Cast(); + }).OrderBy(c => c.CommandName)) + { + commandsLog.AppendFormat(" * {0} - {1}\n", command.CommandName, command.Description); } "chocolatey".Log().Info(@"This is a listing of all of the different things you can pass to choco. diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index a6f873e81b..495f0d656d 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -73,7 +73,7 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon "AllowDowngrade - Should an attempt at downgrading be allowed? Defaults to false.", option => configuration.AllowDowngrade = option != null) .Add("m|sxs|sidebyside|side-by-side|allowmultiple|allow-multiple|allowmultipleversions|allow-multiple-versions", - "AllowMultipleVersions - Should multiple versions of a package be installed? Defaults to false.", + "AllowMultipleVersions - Should multiple versions of a package be installed? Defaults to false. (DEPRECATED)", option => configuration.AllowMultipleVersions = option != null) .Add("i|ignoredependencies|ignore-dependencies", "IgnoreDependencies - Ignore dependencies when installing package(s). Defaults to false.", @@ -108,7 +108,7 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon { if (option != null) configuration.Features.AllowEmptyChecksums = true; }) - .Add("allowemptychecksumsecure|allowemptychecksumssecure|allow-empty-checksums-secure", + .Add("allowemptychecksumsecure|allowemptychecksumssecure|allow-empty-checksums-secure", "Allow Empty Checksums Secure - Allow packages to have empty checksums for downloaded resources from secure locations (HTTPS). Overrides the default feature '{0}' set to '{1}'. Available in 0.10.0+.".format_with(ApplicationParameters.Features.AllowEmptyChecksumsSecure, configuration.Features.AllowEmptyChecksumsSecure.to_string()), option => { @@ -146,20 +146,20 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.UsePackageExitCodes = false; } }) - .Add("usepackagecodes|usepackageexitcodes|use-package-codes|use-package-exit-codes", + .Add("usepackagecodes|usepackageexitcodes|use-package-codes|use-package-exit-codes", "UsePackageExitCodes - Package scripts can provide exit codes. Use those for choco's exit code when non-zero (this value can come from a dependency package). Chocolatey defines valid exit codes as 0, 1605, 1614, 1641, 3010. Overrides the default feature '{0}' set to '{1}'. Available in 0.9.10+.".format_with(ApplicationParameters.Features.UsePackageExitCodes, configuration.Features.UsePackageExitCodes.to_string()), option => configuration.Features.UsePackageExitCodes = option != null ) - .Add("stoponfirstfailure|stop-on-first-failure|stop-on-first-package-failure", + .Add("stoponfirstfailure|stop-on-first-failure|stop-on-first-package-failure", "Stop On First Package Failure - stop running install, upgrade or uninstall on first package failure instead of continuing with others. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.StopOnFirstPackageFailure, configuration.Features.StopOnFirstPackageFailure.to_string()), option => configuration.Features.StopOnFirstPackageFailure = option != null ) - .Add("exitwhenrebootdetected|exit-when-reboot-detected", + .Add("exitwhenrebootdetected|exit-when-reboot-detected", "Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with (ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), option => configuration.Features.ExitOnRebootDetected = option != null ) - .Add("ignoredetectedreboot|ignore-detected-reboot", + .Add("ignoredetectedreboot|ignore-detected-reboot", "Ignore Detected Reboot - Ignore any detected reboots if found. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with (ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), option => @@ -179,6 +179,14 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.UsePackageRepositoryOptimizations = false; } }) + .Add("pin|pinpackage|pin-package", + "Pin Package - Add a pin to the package after install. Available in 1.2.0+", + option => configuration.PinPackage = option != null + ) + .Add("skiphooks|skip-hooks", + "Skip hooks - Do not run hook scripts. Available in 1.2.0+", + option => configuration.SkipHookScripts = option != null + ) ; //todo: #770 package name can be a url / installertype @@ -250,6 +258,10 @@ prompt. In most cases you can still pass options and switches with one Starting in v2.0.0 the shortcut `cinst` will be removed and can not be used to install packages anymore. We recommend you make sure that you always use the full command going forward (`choco install`). + +Side by side installations has been deprecated and will be removed in v2.0.0. +Instead of using side by side installations, distinct packages should be created +if similar functionality is needed going forward. "); "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); @@ -370,7 +382,7 @@ Chocolatey Professional showing private download cache and virus scan diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs index 49df96718e..130b2a3f54 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs @@ -67,7 +67,7 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon "Apply Package Parameters To Dependencies - Should package parameters be applied to dependent packages? Defaults to false.", option => configuration.ApplyPackageParametersToDependencies = option != null) .Add("m|sxs|sidebyside|side-by-side|allowmultiple|allow-multiple|allowmultipleversions|allow-multiple-versions", - "AllowMultipleVersions - Should multiple versions of a package be installed? Defaults to false.", + "AllowMultipleVersions - Should multiple versions of a package be installed? Defaults to false. (DEPRECATED)", option => configuration.AllowMultipleVersions = option != null) .Add("x|forcedependencies|force-dependencies|removedependencies|remove-dependencies", "RemoveDependencies - Uninstall dependencies when uninstalling package(s). Defaults to false.", @@ -133,6 +133,10 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.ExitOnRebootDetected = false; } }) + .Add("skiphooks|skip-hooks", + "Skip hooks - Do not run hook scripts. Available in 1.2.0+", + option => configuration.SkipHookScripts = option != null + ) ; } @@ -208,6 +212,10 @@ to determine how to automatically uninstall software. Starting in v2.0.0 the shortcut `cuninst` will be removed and can not be used to uninstall packages anymore. We recommend you make sure that you always use the full command going forward (`choco uninstall`). + +Side by side installations has been deprecated and support for uninstalling such packages will be removed in v2.0.0. +Instead of using side by side installations, distinct packages should be created +if similar functionality is needed going forward. "); "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index d62af1a892..6accfe2334 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -73,7 +73,7 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon "AllowDowngrade - Should an attempt at downgrading be allowed? Defaults to false.", option => configuration.AllowDowngrade = option != null) .Add("m|sxs|sidebyside|side-by-side|allowmultiple|allow-multiple|allowmultipleversions|allow-multiple-versions", - "AllowMultipleVersions - Should multiple versions of a package be installed? Defaults to false.", + "AllowMultipleVersions - Should multiple versions of a package be installed? Defaults to false. (DEPRECATED)", option => configuration.AllowMultipleVersions = option != null) .Add("i|ignoredependencies|ignore-dependencies", "IgnoreDependencies - Ignore dependencies when upgrading package(s). Defaults to false.", @@ -152,23 +152,23 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.UsePackageExitCodes = false; } }) - .Add("usepackagecodes|usepackageexitcodes|use-package-codes|use-package-exit-codes", + .Add("usepackagecodes|usepackageexitcodes|use-package-codes|use-package-exit-codes", "UsePackageExitCodes - Package scripts can provide exit codes. Use those for choco's exit code when non-zero (this value can come from a dependency package). Chocolatey defines valid exit codes as 0, 1605, 1614, 1641, 3010. Overrides the default feature '{0}' set to '{1}'. Available in 0.9.10+.".format_with(ApplicationParameters.Features.UsePackageExitCodes, configuration.Features.UsePackageExitCodes.to_string()), option => configuration.Features.UsePackageExitCodes = option != null ) - .Add("except=", + .Add("except=", "Except - a comma-separated list of package names that should not be upgraded when upgrading 'all'. Overrides the configuration setting '{0}' set to '{1}'. Available in 0.9.10+.".format_with(ApplicationParameters.ConfigSettings.UpgradeAllExceptions, configuration.UpgradeCommand.PackageNamesToSkip.to_string()), option => configuration.UpgradeCommand.PackageNamesToSkip = option.remove_surrounding_quotes() ) - .Add("stoponfirstfailure|stop-on-first-failure|stop-on-first-package-failure", + .Add("stoponfirstfailure|stop-on-first-failure|stop-on-first-package-failure", "Stop On First Package Failure - stop running install, upgrade or uninstall on first package failure instead of continuing with others. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.StopOnFirstPackageFailure, configuration.Features.StopOnFirstPackageFailure.to_string()), option => configuration.Features.StopOnFirstPackageFailure = option != null ) - .Add("skip-if-not-installed|only-upgrade-installed|skip-when-not-installed", + .Add("skip-if-not-installed|only-upgrade-installed|skip-when-not-installed", "Skip Packages Not Installed - if a package is not installed, do not install it during the upgrade process. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with(ApplicationParameters.Features.SkipPackageUpgradesWhenNotInstalled, configuration.Features.SkipPackageUpgradesWhenNotInstalled.to_string()), option => configuration.Features.SkipPackageUpgradesWhenNotInstalled = option != null ) - .Add("install-if-not-installed", + .Add("install-if-not-installed", "Install Missing Packages When Not Installed - if a package is not installed, install it as part of running upgrade (typically default behavior). Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with(ApplicationParameters.Features.SkipPackageUpgradesWhenNotInstalled, configuration.Features.SkipPackageUpgradesWhenNotInstalled.to_string()), option => { @@ -177,28 +177,28 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.SkipPackageUpgradesWhenNotInstalled = false; } }) - .Add("exclude-pre|exclude-prerelease|exclude-prereleases", + .Add("exclude-pre|exclude-prerelease|exclude-prereleases", "Exclude Prerelease - Should prerelease be ignored for upgrades? Will be ignored if you pass `--pre`. Available in 0.10.4+.", option => configuration.UpgradeCommand.ExcludePrerelease = option != null ) - .Add("userememberedargs|userememberedarguments|userememberedoptions|use-remembered-args|use-remembered-arguments|use-remembered-options", + .Add("userememberedargs|userememberedarguments|userememberedoptions|use-remembered-args|use-remembered-arguments|use-remembered-options", "Use Remembered Options for Upgrade - use the arguments and options used during install for upgrade. Does not override arguments being passed at runtime. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.UseRememberedArgumentsForUpgrades, configuration.Features.UseRememberedArgumentsForUpgrades.to_string()), option => { if (option != null) configuration.Features.UseRememberedArgumentsForUpgrades = true; }) - .Add("ignorerememberedargs|ignorerememberedarguments|ignorerememberedoptions|ignore-remembered-args|ignore-remembered-arguments|ignore-remembered-options", + .Add("ignorerememberedargs|ignorerememberedarguments|ignorerememberedoptions|ignore-remembered-args|ignore-remembered-arguments|ignore-remembered-options", "Ignore Remembered Options for Upgrade - ignore the arguments and options used during install for upgrade. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.UseRememberedArgumentsForUpgrades, configuration.Features.UseRememberedArgumentsForUpgrades.to_string()), option => { if (option != null) configuration.Features.UseRememberedArgumentsForUpgrades = false; }) - .Add("exitwhenrebootdetected|exit-when-reboot-detected", + .Add("exitwhenrebootdetected|exit-when-reboot-detected", "Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with (ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), option => configuration.Features.ExitOnRebootDetected = option != null ) - .Add("ignoredetectedreboot|ignore-detected-reboot", + .Add("ignoredetectedreboot|ignore-detected-reboot", "Ignore Detected Reboot - Ignore any detected reboots if found. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with (ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), option => @@ -208,7 +208,7 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.ExitOnRebootDetected = false; } }) - .Add("disable-repository-optimizations|disable-package-repository-optimizations", + .Add("disable-repository-optimizations|disable-package-repository-optimizations", "Disable Package Repository Optimizations - Do not use optimizations for reducing bandwidth with repository queries during package install/upgrade/outdated operations. Should not generally be used, unless a repository needs to support older methods of query. When disabled, this makes queries similar to the way they were done in Chocolatey v0.10.11 and before. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.14+.".format_with (ApplicationParameters.Features.UsePackageRepositoryOptimizations, configuration.Features.UsePackageRepositoryOptimizations.to_string()), option => @@ -218,6 +218,14 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.UsePackageRepositoryOptimizations = false; } }) + .Add("pin|pinpackage|pin-package", + "Pin Package - Add a pin to the package after upgrade. Available in 1.2.0+", + option => configuration.PinPackage = option != null + ) + .Add("skiphooks|skip-hooks", + "Skip hooks - Do not run hook scripts. Available in 1.2.0+", + option => configuration.SkipHookScripts = option != null + ) ; } @@ -281,6 +289,10 @@ prompt. In most cases you can still pass options and switches with one Starting in v2.0.0 the shortcut `cup` will be removed and can not be used to upgrade or install packages anymore. We recommend you make sure that you always use the full command going forward (`choco upgrade`). + +Side by side installations has been deprecated and will be removed in v2.0.0. +Instead of using side by side installations, distinct packages should be created +if similar functionality is needed going forward. "); "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 172301dcbd..549ac89cb8 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,12 +30,15 @@ namespace chocolatey.infrastructure.app.configuration [Serializable] public class ChocolateyConfiguration { + [NonSerialized] + private ChocolateyConfiguration _originalConfiguration; + public ChocolateyConfiguration() { RegularOutput = true; PromptForConfirmation = true; DisableCompatibilityChecks = false; - SourceType = SourceType.normal; + SourceType = SourceTypes.NORMAL; Information = new InformationCommandConfiguration(); Features = new FeaturesConfiguration(); NewCommand = new NewCommandConfiguration(); @@ -58,6 +61,86 @@ public ChocolateyConfiguration() #endif } + /// + /// Creates a backup of the current version of the configuration class. + /// + /// One or more objects in the class or child classes are not serializable. + public void start_backup() + { + // We do this the easy way to ensure that we have a clean copy + // of the original configuration file. + _originalConfiguration = this.deep_copy(); + } + + /// + /// Restore the backup that has previously been created to the initial + /// state, without making the class reference types the same to prevent + /// the initial configuration class being updated at the same time if a + /// value changes. + /// + /// Whether a backup that was previously made should be removed after resetting the configuration. + /// No backup has been created before trying to reset the current configuration, and removal of the backup was not requested. + /// + /// This call may make quite a lot of allocations on the Gen0 heap, as such + /// it is best to keep the calls to this method at a minimum. + /// + public void reset_config(bool removeBackup = false) + { + if (_originalConfiguration == null) + { + if (removeBackup) + { + // If we will also be removing the backup, we do not care if it is already + // null or not, as that is the intended state when this method returns. + return; + } + + throw new InvalidOperationException("No backup has been created before trying to reset the current configuration, and removal of the backup was not requested."); + } + + var t = this.GetType(); + + foreach (var property in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + try + { + var originalValue = property.GetValue(_originalConfiguration, new object[0]); + + if (removeBackup || property.DeclaringType.IsPrimitive || property.DeclaringType.IsValueType || property.DeclaringType == typeof(string)) + { + // If the property is a primitive, a value type or a string, then a copy of the value + // will be created by the .NET Runtime automatically, and we do not need to create a deep clone of the value. + // Additionally, if we will be removing the backup there is no need to create a deep copy + // for any reference types, as such we also set the reference itself so it is not needed + // to allocate more memory. + property.SetValue(this, originalValue, new object[0]); + } + else if (originalValue != null) + { + // We need to do a deep copy of the value so it won't copy the reference itself, + // but rather the actual values we are interested in. + property.SetValue(this, originalValue.deep_copy(), new object[0]); + } + else + { + property.SetValue(this, null, new object[0]); + } + } + catch (Exception ex) + { + throw new ApplicationException("Unable to restore the value for the property '{0}'.".format_with(property.Name), ex); + } + } + + if (removeBackup) + { + // It is enough to set the original configuration to null to + // allow GC to clean it up the next time it runs on the stored Generation + // Heap Table. + _originalConfiguration = null; + } + } + // overrides public override string ToString() { @@ -69,7 +152,6 @@ public override string ToString() output to a gist for review."); output_tostring(properties, GetType().GetProperties(), this, ""); return properties.ToString(); - } private void output_tostring(StringBuilder propertyValues, IEnumerable properties, object obj, string prepend) @@ -145,6 +227,7 @@ private void append_output(StringBuilder propertyValues, string append) // configuration set variables public string CacheLocation { get; set; } + public bool ContainsLegacyPackageInstalls { get; set; } public int CommandExecutionTimeoutSeconds { get; set; } public int WebRequestTimeoutSeconds { get; set; } @@ -154,7 +237,8 @@ private void append_output(StringBuilder propertyValues, string append) /// One or more source locations set by configuration or by command line. Separated by semi-colon /// public string Sources { get; set; } - public SourceType SourceType { get; set; } + + public string SourceType { get; set; } // top level commands @@ -164,6 +248,7 @@ private void append_output(StringBuilder propertyValues, string append) public bool Force { get; set; } public bool Noop { get; set; } public bool HelpRequested { get; set; } + /// /// Gets or sets a value indicating whether parsing was successful (everything parsed) or not. /// @@ -177,6 +262,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// true for regular output; false for limited output. public bool RegularOutput { get; set; } + /// /// Gets or sets a value indicating whether console logging should be supressed. /// This is for use by API calls which surface results in alternate forms. @@ -184,6 +270,7 @@ private void append_output(StringBuilder propertyValues, string append) /// true for no output; false for regular or limited output. /// This has only been implemented for NuGet List public bool QuietOutput { get; set; } + public bool PromptForConfirmation { get; set; } /// @@ -206,9 +293,11 @@ private void append_output(StringBuilder propertyValues, string append) // command level options public string Version { get; set; } + public bool AllVersions { get; set; } public bool SkipPackageInstallProvider { get; set; } public string OutputDirectory { get; set; } + public bool SkipHookScripts { get; set; } // install/update /// @@ -228,13 +317,17 @@ private void append_output(StringBuilder propertyValues, string append) public bool ApplyPackageParametersToDependencies { get; set; } public bool ApplyInstallArgumentsToDependencies { get; set; } public bool IgnoreDependencies { get; set; } + + [Obsolete("Side by Side installation is deprecated, and is pending removal in v2.0.0")] public bool AllowMultipleVersions { get; set; } + public bool AllowDowngrade { get; set; } public bool ForceDependencies { get; set; } public string DownloadChecksum { get; set; } public string DownloadChecksum64 { get; set; } public string DownloadChecksumType { get; set; } public string DownloadChecksumType64 { get; set; } + public bool PinPackage { get; set; } /// /// Configuration values provided by choco. @@ -242,7 +335,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public InformationCommandConfiguration Information { get; set; } + public InformationCommandConfiguration Information { get; set; } /// /// Configuration related to features and whether they are enabled. @@ -250,7 +343,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public FeaturesConfiguration Features { get; set; } + public FeaturesConfiguration Features { get; set; } /// /// Configuration related specifically to List command @@ -258,7 +351,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public ListCommandConfiguration ListCommand { get; set; } + public ListCommandConfiguration ListCommand { get; set; } /// /// Configuration related specifically to Upgrade command @@ -266,7 +359,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public UpgradeCommandConfiguration UpgradeCommand { get; set; } + public UpgradeCommandConfiguration UpgradeCommand { get; set; } /// /// Configuration related specifically to New command @@ -274,7 +367,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public NewCommandConfiguration NewCommand { get; set; } + public NewCommandConfiguration NewCommand { get; set; } /// /// Configuration related specifically to Source command @@ -282,7 +375,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public SourcesCommandConfiguration SourceCommand { get; set; } + public SourcesCommandConfiguration SourceCommand { get; set; } /// /// Default Machine Sources Configuration @@ -314,7 +407,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public ApiKeyCommandConfiguration ApiKeyCommand { get; set; } + public ApiKeyCommandConfiguration ApiKeyCommand { get; set; } /// /// Configuration related specifically to the Pack command. @@ -330,7 +423,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public PushCommandConfiguration PushCommand { get; set; } + public PushCommandConfiguration PushCommand { get; set; } /// /// Configuration related specifically to Pin command @@ -364,7 +457,7 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public TemplateCommandConfiguration TemplateCommand { get; set; } + public TemplateCommandConfiguration TemplateCommand { get; set; } } [Serializable] @@ -372,6 +465,7 @@ public sealed class InformationCommandConfiguration { // application set variables public PlatformType PlatformType { get; set; } + public Version PlatformVersion { get; set; } public string PlatformName { get; set; } public string ChocolateyVersion { get; set; } @@ -434,6 +528,7 @@ public ListCommandConfiguration() // list public bool LocalOnly { get; set; } + public bool IdOnly { get; set; } public bool IncludeRegistryPrograms { get; set; } public int? Page { get; set; } diff --git a/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs index 6a9c25e0d4..ee4d5b0d02 100644 --- a/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs +++ b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs @@ -58,5 +58,108 @@ public sealed class PackagesConfigFilePackageSetting [XmlAttribute(AttributeName = "disabled")] public bool Disabled { get; set; } + + [XmlAttribute(AttributeName = "pinPackage")] + public bool PinPackage { get; set; } + + [System.ComponentModel.DefaultValue(-1)] + [XmlAttribute(AttributeName = "executionTimeout")] + public int ExecutionTimeout { get; set; } + + [XmlAttribute(AttributeName = "force")] + public bool Force { get; set; } + + [XmlAttribute(AttributeName = "prerelease")] + public bool Prerelease { get; set; } + + [XmlAttribute(AttributeName = "overrideArguments")] + public bool OverrideArguments { get; set; } + + [XmlAttribute(AttributeName = "notSilent")] + public bool NotSilent { get; set; } + + [XmlAttribute(AttributeName = "allowDowngrade")] + public bool AllowDowngrade { get; set; } + + [XmlAttribute(AttributeName = "forceDependencies")] + public bool ForceDependencies { get; set; } + + [XmlAttribute(AttributeName = "skipAutomationScripts")] + public bool SkipAutomationScripts { get; set; } + + [XmlAttribute(AttributeName = "user")] + public string User { get; set; } + + [XmlAttribute(AttributeName = "password")] + public string Password { get; set; } + + [XmlAttribute(AttributeName = "cert")] + public string Cert { get; set; } + + [XmlAttribute(AttributeName = "certPassword")] + public string CertPassword { get; set; } + + [XmlAttribute(AttributeName = "ignoreChecksums")] + public bool IgnoreChecksums { get; set; } + + [XmlAttribute(AttributeName = "allowEmptyChecksums")] + public bool AllowEmptyChecksums { get; set; } + + [XmlAttribute(AttributeName = "allowEmptyChecksumsSecure")] + public bool AllowEmptyChecksumsSecure { get; set; } + + [XmlAttribute(AttributeName = "requireChecksums")] + public bool RequireChecksums { get; set; } + + [XmlAttribute(AttributeName = "downloadChecksum")] + public string DownloadChecksum { get; set; } + + [XmlAttribute(AttributeName = "downloadChecksum64")] + public string DownloadChecksum64 { get; set; } + + [XmlAttribute(AttributeName = "downloadChecksumType")] + public string DownloadChecksumType { get; set; } + + [XmlAttribute(AttributeName = "downloadChecksumType64")] + public string DownloadChecksumType64 { get; set; } + + [XmlAttribute(AttributeName = "ignorePackageExitCodes")] + public bool IgnorePackageExitCodes { get; set; } + + [XmlAttribute(AttributeName = "usePackageExitCodes")] + public bool UsePackageExitCodes { get; set; } + + [XmlAttribute(AttributeName = "stopOnFirstFailure")] + public bool StopOnFirstFailure { get; set; } + + [XmlAttribute(AttributeName = "exitWhenRebootDetected")] + public bool ExitWhenRebootDetected { get; set; } + + [XmlAttribute(AttributeName = "ignoreDetectedReboot")] + public bool IgnoreDetectedReboot { get; set; } + + [XmlAttribute(AttributeName = "disableRepositoryOptimizations")] + public bool DisableRepositoryOptimizations { get; set; } + + [XmlAttribute(AttributeName = "acceptLicense")] + public bool AcceptLicense { get; set; } + + [XmlAttribute(AttributeName = "confirm")] + public bool Confirm { get; set; } + + [XmlAttribute(AttributeName = "limitOutput")] + public bool LimitOutput { get; set; } + + [XmlAttribute(AttributeName = "cacheLocation")] + public string CacheLocation { get; set; } + + [XmlAttribute(AttributeName = "failOnStderr")] + public bool FailOnStderr { get; set; } + + [XmlAttribute(AttributeName = "useSystemPowershell")] + public bool UseSystemPowershell { get; set; } + + [XmlAttribute(AttributeName = "noProgress")] + public bool NoProgress { get; set; } } } diff --git a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs index 14c99e518c..9066d004a4 100644 --- a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs +++ b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.domain { + using System; using NuGet; public sealed class ChocolateyPackageInformation @@ -31,6 +32,8 @@ public ChocolateyPackageInformation(IPackage package) public string Arguments { get; set; } public SemanticVersion VersionOverride { get; set; } public bool HasSilentUninstall { get; set; } + + [Obsolete("Side by side installations are deprecated, with removal pending in v2.0.0")] public bool IsSideBySide { get; set; } public bool IsPinned { get; set; } public string ExtraInformation { get; set; } diff --git a/src/chocolatey/infrastructure.app/domain/SourceType.cs b/src/chocolatey/infrastructure.app/domain/SourceType.cs index 884e1e9f50..1a56dccd36 100644 --- a/src/chocolatey/infrastructure.app/domain/SourceType.cs +++ b/src/chocolatey/infrastructure.app/domain/SourceType.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,13 +16,17 @@ namespace chocolatey.infrastructure.app.domain { + using System; + /// /// Special source modifiers that use alternate sources for packages /// + [Obsolete("This source type is no longer used, and only provided for backwards compatibility, instead use SourceTypes class instead.")] public enum SourceType { //this is what it should be when it's not set normal, + webpi, ruby, python, diff --git a/src/chocolatey/infrastructure.app/domain/SourceTypes.cs b/src/chocolatey/infrastructure.app/domain/SourceTypes.cs new file mode 100644 index 0000000000..5273693280 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/SourceTypes.cs @@ -0,0 +1,67 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.domain +{ + /// + /// This class contains the default source types that are implemented in + /// the Chocolatey CLI codebase. This is replacing the enumeration previously + /// available through . + /// + public static class SourceTypes + { + /// + /// The source is of type Cygwin and need to be handled by an + /// alternative source runner. + /// + public const string CYGWIN = "cygwin"; + + /// + /// The source is a normal type, ie a chocolatey/nuget source. + /// + public const string NORMAL = "normal"; + + /// + /// The source is of type Python and need to be handled by an + /// alternative source runner. + /// + public const string PYTHON = "python"; + + /// + /// The source is of type Ruby and need to be handled by an + /// alternative source runner. + /// + public const string RUBY = "ruby"; + + /// + /// The source is of type Web PI and need to be handled by an + /// alternative source runner. + /// + public const string WEBPI = "webpi"; + + /// + /// The source is a windows feature and is only provided as an + /// alias for + /// + public const string WINDOWS_FEATURE = "windowsfeature"; + + /// + /// The source is a windows feature and need to be handled by an + /// alternative source runner. + /// + public const string WINDOWS_FEATURES = "windowsfeatures"; + } +} diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyLocalPackageRepository.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyLocalPackageRepository.cs index fac183450d..6525b1a091 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyLocalPackageRepository.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyLocalPackageRepository.cs @@ -48,42 +48,6 @@ public ChocolateyLocalPackageRepository(IPackagePathResolver pathResolver, IFile { } - public bool IgnoreVersionedDirectories { get; set; } - - public override IQueryable GetPackages() - { - var packages = base.GetPackages(); - - if (IgnoreVersionedDirectories) - { - packages = packages.Where(ExcludeVersionedDirectories).ToList().AsQueryable(); - } - - return packages; - } - - public override IPackage FindPackage(string packageId, SemanticVersion version) - { - if (IgnoreVersionedDirectories) - { - return FindPackagesById(packageId).FirstOrDefault(package => package.Version >= version); - } - - return base.FindPackage(packageId, version); - } - - public override IEnumerable FindPackagesById(string packageId) - { - var packages = base.FindPackagesById(packageId); - - if (IgnoreVersionedDirectories) - { - packages = packages.Where(ExcludeVersionedDirectories); - } - - return packages; - } - public override void AddPackage(IPackage package) { string packageFilePath = GetPackageFilePath(package); @@ -108,19 +72,6 @@ public override void AddPackage(IPackage package) } } - private bool ExcludeVersionedDirectories(IPackage package) - { - var directoryPath = PathResolver.GetInstallPath(package); - if (string.IsNullOrWhiteSpace(directoryPath)) - { - return true; - } - - var directoryName = Path.GetFileName(directoryPath); - - return string.Compare(directoryName, package.Id + "." + package.Version, StringComparison.OrdinalIgnoreCase) != 0; - } - private string GetManifestFilePath(string packageId, SemanticVersion version) { string packageDirectory = PathResolver.GetPackageDirectory(packageId, version); diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyPackagePathResolver.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyPackagePathResolver.cs index 9579ed925c..f612861b9e 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyPackagePathResolver.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyPackagePathResolver.cs @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.nuget { + using System; using System.IO; using NuGet; @@ -24,8 +25,16 @@ namespace chocolatey.infrastructure.app.nuget public sealed class ChocolateyPackagePathResolver : DefaultPackagePathResolver { private readonly IFileSystem _nugetFileSystem; + + [Obsolete("Side by Side installations are deprecated, and is pending removal in v2.0.0")] public bool UseSideBySidePaths { get; set; } + public ChocolateyPackagePathResolver(IFileSystem nugetFileSystem) + : this(nugetFileSystem, useSideBySidePaths: false) + { + } + + [Obsolete("Initializing using side by side installation enabled is deprecated. Use overload without useSideBySidePaths instead.")] public ChocolateyPackagePathResolver(IFileSystem nugetFileSystem, bool useSideBySidePaths) : base(nugetFileSystem, useSideBySidePaths) { @@ -47,6 +56,7 @@ public override string GetPackageDirectory(string packageId, SemanticVersion ver return GetPackageDirectory(packageId, version, UseSideBySidePaths); } + [Obsolete("Side by Side installations are deprecated, and is pending removal in v2.0.0")] public string GetPackageDirectory(string packageId, SemanticVersion version, bool useVersionInPath) { string directory = packageId; diff --git a/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs b/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs new file mode 100644 index 0000000000..8436e7e31f --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs @@ -0,0 +1,81 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using chocolatey.infrastructure.app.configuration; + using chocolatey.infrastructure.app.nuget; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.app.tasks; + using chocolatey.infrastructure.app.validations; + using chocolatey.infrastructure.commands; + using chocolatey.infrastructure.configuration; + using chocolatey.infrastructure.services; + using chocolatey.infrastructure.tasks; + using chocolatey.infrastructure.validations; + using NuGet; + using CryptoHashProvider = cryptography.CryptoHashProvider; + using IFileSystem = filesystem.IFileSystem; + using IHashProvider = cryptography.IHashProvider; + + internal class ChocolateyRegistrationModule : IExtensionModule + { + public void register_dependencies(IContainerRegistrator registrator, ChocolateyConfiguration configuration) + { + // Should be replaced by a extension registration instead of a full configuration + // Which would be possible to override by any extension, which we most likely do + // not want in the long run. + registrator.register_service(); + registrator.register_service(); + + //nuget + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_instance((resolver) => new CryptoHashProvider(resolver.resolve())); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_service(); + registrator.register_instance(() => new adapters.CustomString(string.Empty)); + + registrator.register_service( + typeof(INugetService), + typeof(WebPiService), + typeof(WindowsFeatureService), + typeof(CygwinService), + typeof(PythonService), + typeof(RubyGemsService)); + + registrator.register_service(); + + registrator.register_service( + typeof(RemovePendingPackagesTask)); + + registrator.register_service( + typeof(GlobalConfigurationValidation), + typeof(SystemStateValidation)); + } + } +} diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index ff21ee6da4..b9396e5e40 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,135 +16,205 @@ namespace chocolatey.infrastructure.app.registration { + using System; using System.Collections.Generic; - using infrastructure.events; - using infrastructure.tasks; + using System.Linq; + using System.Reflection; + using chocolatey.infrastructure.app.builders; + using chocolatey.infrastructure.app.configuration; + using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.information; + using chocolatey.infrastructure.licensing; + using chocolatey.infrastructure.logging; + using chocolatey.infrastructure.services; + using infrastructure.configuration; using NuGet; using SimpleInjector; - using adapters; - using commands; - using filesystem; - using infrastructure.commands; - using infrastructure.configuration; - using infrastructure.services; - using infrastructure.validations; - using nuget; - using services; - using tasks; - using validations; - using CryptoHashProvider = cryptography.CryptoHashProvider; - using IFileSystem = filesystem.IFileSystem; - using IHashProvider = cryptography.IHashProvider; + using Assembly = adapters.Assembly; // ReSharper disable InconsistentNaming /// /// The main inversion container registration for the application. Look for other container bindings in client projects. /// - public sealed class ContainerBinding + public sealed partial class ContainerBinding { /// /// Loads the module into the kernel. /// - public void RegisterComponents(Container container) + public IEnumerable RegisterComponents(Container container) { + var availableExtensions = new List(); + var configuration = Config.get_configuration_settings(); - container.Register(() => configuration, Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - - //nuget - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(() => new CryptoHashProvider(container.GetInstance()), Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); - container.Register(() => new CustomString(string.Empty)); - - //todo: #2572 refactor - this should be autowired - container.Register>(() => + var registrator = new SimpleInjectorContainerRegistrator(); + + // We can not resolve this class, as that wil prevent future registrations + var fileSystem = new DotNetFileSystem(); + var xmlService = new XmlService(fileSystem, new cryptography.CryptoHashProvider(fileSystem)); + + var mainRegistrator = new ChocolateyRegistrationModule(); + registrator.CanReplaceRegister = true; + registrator.register_instance(() => fileSystem); + registrator.register_instance(() => Config.get_configuration_settings()); + mainRegistrator.register_dependencies(registrator, configuration); + registrator.register_assembly_commands(Assembly.GetExecutingAssembly()); + registrator.CanReplaceRegister = false; + + var assemblies = fileSystem.get_extension_assemblies(); + var currentAssemblyVersionString = VersionInformation.get_current_assembly_version(); + Version currentAssemblyVersion; + if (!Version.TryParse(currentAssemblyVersionString, out currentAssemblyVersion)) + { + currentAssemblyVersion = new Version("0.0.0.0"); + } + + var arguments = Environment.GetCommandLineArgs(); + + var disableCompatibilityChecks = ConfigurationBuilder.is_compatibility_checks_disabled(fileSystem, xmlService) || + arguments.Any(a => a.is_equal_to("--skip-compatibility-checks")); + + var chocoVersion = new SemanticVersion(VersionInformation.get_current_assembly_version()); + registrator = register_extensions(availableExtensions, configuration, registrator, assemblies, currentAssemblyVersion, chocoVersion, disableCompatibilityChecks); + + container = registrator.build_container(container); + + var availableExtensionsArray = availableExtensions.Distinct().ToArray(); + + foreach (var extension in availableExtensionsArray) + { + this.Log().Debug("Loaded extension {0} v{1} with status '{2}'", + extension.Name, + extension.Version, + extension.Status); + } + + container.RegisterAll(availableExtensionsArray.AsEnumerable()); + + return availableExtensionsArray; + } + + private SimpleInjectorContainerRegistrator register_extensions(List availableExtensions, ChocolateyConfiguration configuration, SimpleInjectorContainerRegistrator registrator, IEnumerable assemblies, Version currentAssemblyVersion, SemanticVersion chocoVersion, bool disableCompatibilityChecks) + { + foreach (var assembly in assemblies) + { + var assemblyName = assembly.GetName().Name; + var extensionInformation = new ExtensionInformation(assembly); + var minimumChocolateyVersionString = VersionInformation.get_minimum_chocolatey_version(assembly); + Version minimumChocolateyVersion; + + if (!disableCompatibilityChecks && Version.TryParse(minimumChocolateyVersionString, out minimumChocolateyVersion) && currentAssemblyVersion < minimumChocolateyVersion) { - var list = new List - { - new ChocolateyListCommand(container.GetInstance()), - new ChocolateyHelpCommand(container), - new ChocolateyInfoCommand(container.GetInstance()), - new ChocolateyInstallCommand(container.GetInstance()), - new ChocolateyPinCommand(container.GetInstance(), container.GetInstance(), container.GetInstance()), - new ChocolateyOutdatedCommand(container.GetInstance()), - new ChocolateyUpgradeCommand(container.GetInstance()), - new ChocolateyUninstallCommand(container.GetInstance()), - new ChocolateyPackCommand(container.GetInstance()), - new ChocolateyPushCommand(container.GetInstance(), container.GetInstance()), - new ChocolateyNewCommand(container.GetInstance()), - new ChocolateySourceCommand(container.GetInstance()), - new ChocolateyConfigCommand(container.GetInstance()), - new ChocolateyFeatureCommand(container.GetInstance()), - new ChocolateyApiKeyCommand(container.GetInstance()), - new ChocolateyUnpackSelfCommand(container.GetInstance()), - new ChocolateyExportCommand(container.GetInstance(), container.GetInstance()), - new ChocolateyTemplateCommand(container.GetInstance()), - new ChocolateyVersionCommand(container.GetInstance()), - new ChocolateyUpdateCommand(container.GetInstance()) - }; - return list.AsReadOnly(); - }, Lifestyle.Singleton); - - container.Register>(() => + this.Log().Warn(@" +You are running a version of Chocolatey that may not be compatible with the the extension {0} version {1}. +The Chocolatey version required is {2}, the extension will not be loaded. + +You can override this compatibility check and force loading the extension by passing in the --skip-compatibility-checks +option when executing a command, or by enabling the DisableCompatibilityChecks feature with the following command: +choco feature enable --name=""disableCompatibilityChecks""", + extensionInformation.Name, + extensionInformation.Version, + minimumChocolateyVersion + ); + + extensionInformation.Status = ExtensionStatus.Disabled; + availableExtensions.Add(extensionInformation); + continue; + } + + var hasRegisteredDependencies = false; + + try { - var list = new List - { - container.GetInstance(), - new WebPiService(container.GetInstance(), container.GetInstance()), - new WindowsFeatureService(container.GetInstance(), container.GetInstance(), container.GetInstance()), - new CygwinService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()), - new PythonService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()), - new RubyGemsService(container.GetInstance(), container.GetInstance()) - }; - return list.AsReadOnly(); - }, Lifestyle.Singleton); - - - container.Register(Lifestyle.Singleton); - EventManager.initialize_with(container.GetInstance); - - container.Register>( - () => - { - var list = new List + var registrationClasses = assembly.get_extension_modules(); + + this.Log().Debug("Trying to load and register extension '{0}'", assemblyName); + + // We make a clone of the existing registrator to prevent + // the registrations being applied if something fails for + // the extension + var clonedRegistrator = (SimpleInjectorContainerRegistrator)registrator.Clone(); + clonedRegistrator.CanReplaceRegister = true; + + foreach (var registration in registrationClasses) { - new RemovePendingPackagesTask(container.GetInstance(), container.GetInstance()) - }; + if (clonedRegistrator.RegistrationFailed) + { + break; + } - return list.AsReadOnly(); - }, - Lifestyle.Singleton); + this.Log().Debug("Calling registration module '{0}' in extension '{1}'!", registration.GetType().Name, assemblyName); + clonedRegistrator._validationHandlers.Clear(); + clonedRegistrator.register_validator((instanceType) => validate_minimum_chocolatey_version(instanceType, chocoVersion)); + registration.register_dependencies(clonedRegistrator, configuration.deep_copy()); + hasRegisteredDependencies = !clonedRegistrator.RegistrationFailed; + } - container.Register>( - () => + if (hasRegisteredDependencies) + { + clonedRegistrator.register_assembly_commands(assembly); + hasRegisteredDependencies = !clonedRegistrator.RegistrationFailed; + } + + if (hasRegisteredDependencies && !clonedRegistrator.RegistrationFailed) + { + registrator = clonedRegistrator; + extensionInformation.Status = ExtensionStatus.Loaded; + } + else if (clonedRegistrator.RegistrationFailed) + { + extensionInformation.Status = ExtensionStatus.Failed; + } + else + { + // In this case we can assume there was no registration class, + // as such we just ignore adding it as an available extension. + continue; + } + } + catch (Exception ex) + { + this.Log().Error("Unable to load extension {0}: {1}.", assemblyName, ex.Message); + this.Log().Error(ChocolateyLoggers.LogFileOnly, ex.StackTrace); + extensionInformation.Status = ExtensionStatus.Failed; + } + finally { - var list = new List + registrator.CanReplaceRegister = false; + + if (hasRegisteredDependencies || !extensionInformation.Name.is_equal_to("chocolatey.licensed")) { - new GlobalConfigurationValidation(), - new SystemStateValidation(container.GetInstance()) - }; + availableExtensions.Add(extensionInformation); + } + } + } + + return registrator; + } + + private bool validate_minimum_chocolatey_version(Type instanceType, SemanticVersion chocoVersion) + { + if (instanceType == null) + { + return false; + } + + var methodImpl = instanceType.GetMethod("supports_chocolatey", BindingFlags.Static | BindingFlags.Public); + + if (methodImpl == null) + { + return true; + } - return list.AsReadOnly(); - }, - Lifestyle.Singleton); + try + { + return (bool)methodImpl.Invoke(null, new object[] { chocoVersion }); + } + catch (Exception) + { + return false; + } } } diff --git a/src/chocolatey/infrastructure.app/registration/IContainerRegistrator.cs b/src/chocolatey/infrastructure.app/registration/IContainerRegistrator.cs new file mode 100644 index 0000000000..6fc28e0d26 --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/IContainerRegistrator.cs @@ -0,0 +1,40 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +namespace chocolatey.infrastructure.app.registration +{ + using System; + + public interface IContainerRegistrator + { + bool RegistrationFailed { get; } + + void register_validator(Func validation_func); + + void register_service(bool transient = false) + where TImplementation : class, TService; + + void register_service(params Type[] types); + + void register_instance(Func instance) + where TImplementation : class; + + void register_instance(Func instance) + where TImplementation : class, TService; + + void register_instance(Func instance) + where TImplementation : class, TService; + } +} diff --git a/src/chocolatey/infrastructure.app/registration/IContainerResolver.cs b/src/chocolatey/infrastructure.app/registration/IContainerResolver.cs new file mode 100644 index 0000000000..8555831e3d --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/IContainerResolver.cs @@ -0,0 +1,29 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using System.Collections.Generic; + + public interface IContainerResolver + { + TService resolve() + where TService : class; + + IEnumerable resolve_all() + where TService : class; + } +} diff --git a/src/chocolatey/infrastructure.app/registration/IExtensionConfiguration.cs b/src/chocolatey/infrastructure.app/registration/IExtensionConfiguration.cs new file mode 100644 index 0000000000..0ae88bcb14 --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/IExtensionConfiguration.cs @@ -0,0 +1,39 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// Placeholder for the future to prevent the need to do a breaking release of + /// Chocolatey Licensed Extension. + /// + public interface IExtensionConfiguration + { + /// + /// Creates the initial configuration for this extension. + /// This will be automatically populated with the correct values + /// from the configuration file by Chocolatey CLI. + /// + /// The initial configuration for the settings. + /// This is not used, and is only a placeholder for the future. + object create_initial_extension_configuration(); + } +} diff --git a/src/chocolatey/infrastructure.app/registration/IExtensionEnvironment.cs b/src/chocolatey/infrastructure.app/registration/IExtensionEnvironment.cs new file mode 100644 index 0000000000..e4d1d5c2ec --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/IExtensionEnvironment.cs @@ -0,0 +1,35 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using System.Collections.Generic; + using chocolatey.infrastructure.app.configuration; + + /// + /// Placeholder for the future to prevent the need for a breaking release of Chocolatey Licensed Extension. + /// + public interface IExtensionEnvironment + { + /// + /// Returns all of the availabe configuration values that are related to the implementing Chocolatey extension. + /// + /// The configuration used for the entire chocolatey ecosystem. + /// The configuration values that needs to be set as environment variables. + /// This is not used, and is only a placeholder for the future. + IDictionary get_environment_configuration(ChocolateyConfiguration config); + } +} diff --git a/src/chocolatey/infrastructure.app/registration/IExtensionModule.cs b/src/chocolatey/infrastructure.app/registration/IExtensionModule.cs new file mode 100644 index 0000000000..56ec7d85a1 --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/IExtensionModule.cs @@ -0,0 +1,25 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using chocolatey.infrastructure.app.configuration; + + public interface IExtensionModule + { + void register_dependencies(IContainerRegistrator registrator, ChocolateyConfiguration configuration); + } +} diff --git a/src/chocolatey/infrastructure.app/registration/SimpleInjectorContainerRegistrator.cs b/src/chocolatey/infrastructure.app/registration/SimpleInjectorContainerRegistrator.cs new file mode 100644 index 0000000000..aa7d227088 --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/SimpleInjectorContainerRegistrator.cs @@ -0,0 +1,488 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using chocolatey.infrastructure.adapters; + using chocolatey.infrastructure.app.attributes; + using infrastructure.commands; + using infrastructure.events; + using infrastructure.services; + using NuGet; + using SimpleInjector; + using Assembly = adapters.Assembly; + + internal sealed class SimpleInjectorContainerRegistrator : IContainerRegistrator, ICloneable + { + internal List> _validationHandlers = new List>(); + + // We need to store the aliases for the commands to prevent them from + // being overridden when the original class implementing these hasn't been removed. + private HashSet _allCommands = new HashSet(); + + private IAssembly _chocoAssembly; + private ConcurrentDictionary> _instanceActionRegistrations = new ConcurrentDictionary>(); + private bool _isBuilt; + private ConcurrentDictionary> _multiServices = new ConcurrentDictionary>(); + private ConcurrentDictionary _registeredCommands = new ConcurrentDictionary(); + private ConcurrentDictionary _singletonServices = new ConcurrentDictionary(); + private ConcurrentDictionary _transientServices = new ConcurrentDictionary(); + + public SimpleInjectorContainerRegistrator() + { + _chocoAssembly = Assembly.GetExecutingAssembly(); + } + + public bool CanReplaceRegister { get; internal set; } + + public bool RegistrationFailed { get; internal set; } + + // We add a specific clone handler due to some fields can not be + // serialized through the deep_copy extension helper. + public object Clone() + { + var cloned = (SimpleInjectorContainerRegistrator)MemberwiseClone(); + cloned._allCommands = _allCommands.deep_copy(); + cloned._instanceActionRegistrations = new ConcurrentDictionary>(); + + foreach (var instanceRegistration in _instanceActionRegistrations) + { + var key = instanceRegistration.Key; + var value = (Func)instanceRegistration.Value.Clone(); + + cloned._instanceActionRegistrations.TryAdd(key, value); + } + + cloned._multiServices = _multiServices.deep_copy(); + cloned._registeredCommands = _registeredCommands.deep_copy(); + cloned._singletonServices = _singletonServices.deep_copy(); + cloned._transientServices = _transientServices.deep_copy(); + cloned._validationHandlers = new List>(); + + return cloned; + } + + public void register_assembly_commands(IAssembly assembly) + { + try + { + var types = assembly.get_loadable_types() + .Where(t => t.IsClass && !t.IsAbstract && typeof(ICommand).IsAssignableFrom(t) && t.GetCustomAttribute() != null); + + foreach (var t in types) + { + if (RegistrationFailed) + { + break; + } + + register_command(t); + } + } + catch (Exception ex) + { + this.Log().Warn("Unable to register commands for '{0}'. Continuing without registering commands!", assembly.GetName().Name); + this.Log().Warn(ex.Message); + RegistrationFailed = true; + } + } + + public void register_command(Type commandType) + { + ensure_not_built(); + + if (!can_register_service(commandType)) + { + return; + } + + var commandForAttribute = commandType.GetCustomAttribute(); + + if (commandForAttribute == null) + { + throw new ArgumentException("{0} does not register a specific command!".format_with(commandType.Name)); + } + + if (!commandType.GetInterfaces().Contains(typeof(ICommand))) + { + throw new ArgumentException("{0} does not implement the interface 'ICommand'. All commands must implement this interface!".format_with(commandType.Name)); + } + + var commandName = get_command_name(commandForAttribute); + + _registeredCommands.AddOrUpdate(commandName, addValueFactory: (key) => + { + var commandTypeAttributes = commandType.GetCustomAttributes(typeof(CommandForAttribute), false).Cast(); + validate_commands_replacement(commandTypeAttributes); + + add_commands(commandTypeAttributes); + + this.Log().Debug("Registering new command '{0}' in assembly '{1}'", + commandName, + commandType.Assembly.GetName().Name); + return commandType; + }, updateValueFactory: (key, value) => + { + if (!value.Assembly.FullName.is_equal_to(_chocoAssembly.FullName) && !commandType.IsAssignableFrom(value)) + { + // We do not allow extensions to override eachothers command. + // This may change in the future to allow multiple command handlers. + // However we do not want to throw an exception in this case. + this.Log().Debug("Command '{0}' implementation in assembly '{1}' tried to replace command in extension '{2}'. Ignoring replacement...", + commandName, + commandType.Assembly.GetName().Name, + value.Assembly.GetName().Name); + return value; + } + + validate_replace_permissions(); + + var removedCommands = remove_commands(value, commandName).ToList(); + + try + { + var commandTypeAttributes = commandType.GetCustomAttributes(typeof(CommandForAttribute), false).Cast(); + + validate_commands_replacement(commandTypeAttributes); + + this.Log().Debug("Replacing existing command '{0}' from assembly '{1}' with implementation in assembly '{2}'", + commandName, + value.Assembly.GetName().Name, + commandType.Assembly.GetName().Name); + add_commands(commandTypeAttributes); + } + catch (Exception ex) + { + RegistrationFailed = true; + _allCommands.AddRange(removedCommands); + throw ex; + } + + return commandType; + }); + } + + public void register_instance(Func instance) + where TImplementation : class, TService + { + register_instance((container) => instance()); + } + + public void register_instance(Func instance) + where TImplementation : class, TService + { + register_instance(typeof(TService), (container) => instance(container)); + } + + public void register_instance(Func instance) + where TImplementation : class + { + register_instance(instance); + } + + public void register_service(params Type[] types) + { + foreach (var serviceType in types) + { + register_service(typeof(TImplementation), serviceType); + } + } + + public void register_service(bool transient = false) + where TImplementation : class, TService + { + var interfaceType = typeof(TService); + var serviceType = typeof(TImplementation); + + register_service(interfaceType, serviceType, transient); + } + + public void register_validator(Func validation_func) + { + _validationHandlers.Add(validation_func); + } + + internal Container build_container(Container container) + { + container.RegisterAll(_registeredCommands.Values); + + add_services_to_container(container, _singletonServices, Lifestyle.Singleton); + add_services_to_container(container, _transientServices, Lifestyle.Transient); + + foreach (var multiService in _multiServices) + { + container.RegisterAll(multiService.Key, multiService.Value.AsEnumerable()); + } + + foreach (var instanceAction in _instanceActionRegistrations) + { + container.Register(instanceAction.Key, () => + { + var resolver = container.GetInstance(); + return instanceAction.Value(resolver); + }, Lifestyle.Singleton); + } + + _registeredCommands.Clear(); + _singletonServices.Clear(); + _transientServices.Clear(); + _multiServices.Clear(); + _allCommands.Clear(); + + container.RegisterSingle(); + + EventManager.initialize_with(container.GetInstance); + + _isBuilt = true; + + return container; + } + + private static void add_services_to_container(Container container, ConcurrentDictionary services, Lifestyle lifestyle) + { + foreach (var service in services) + { + container.Register(service.Key, service.Value, lifestyle); + } + } + + private void add_commands(IEnumerable commandTypeAttributes) + { + foreach (var commandFor in commandTypeAttributes) + { + var commandName = commandFor.CommandName.to_lower(); + + if (!_allCommands.Contains(commandName)) + { + _allCommands.Add(commandName); + } + } + } + + private void add_to_multi_services(Type interfaceType, Type serviceType) + { + ensure_not_built(); + validate_service_registration(interfaceType, serviceType, validate_multi_services: false); + + remove_existing_registration(interfaceType); + + _multiServices.AddOrUpdate(interfaceType, new List { serviceType }, (key, value) => + { + this.Log().Debug("Adding new type '{0}' for type '{1}' from assembly '{2}'", + serviceType.Name, + interfaceType.Name, + serviceType.Assembly.GetName().Name); + value.Add(serviceType); + return value; + }); + } + + private bool can_register_service(Type serviceType) + { + foreach (var validator in _validationHandlers) + { + if (!validator(serviceType)) + { + return false; + } + } + + return true; + } + + private void ensure_not_built() + { + if (_isBuilt) + { + throw new ApplicationException("Registration has been completed, as such it is not possible to register any new commands!"); + } + } + + private string get_command_name(CommandForAttribute commandForAttribute) + { + var commandName = commandForAttribute.CommandName.to_lower(); + + // First check if we have stored the actual command + if (_registeredCommands.ContainsKey(commandName)) + { + return commandName; + } + + // If we have not registered a command, but it is in all commands + // this is most likely an alias on an existing command, as such we + // need to iterate through all commands. + if (!_allCommands.Contains(commandName)) + { + return commandName; + } + + foreach (var command in _registeredCommands) + { + var allCommandForAttributes = command.Value.GetCustomAttributes(typeof(CommandForAttribute), inherit: false).Cast(); + + foreach (var aliasCommand in allCommandForAttributes) + { + if (aliasCommand.CommandName.is_equal_to(commandName)) + { + return command.Key; + } + } + } + + // If we have gotten here, that means all commands have a registered + // command for this type, but it can not be found. As such we need to + // throw an error so it can be looked at. + throw new ApplicationException("The command '{0}' has been globally registered, but can not be found!".format_with(commandName)); + } + + private void register_instance(Type serviceType, Func instanceAction) + { + ensure_not_built(); + + validate_service_registration(serviceType, serviceType, validate_multi_services: true); + remove_existing_registration(serviceType); + + _instanceActionRegistrations.AddOrUpdate(serviceType, instanceAction, (key, value) => instanceAction); + } + + private void register_service(Type interfaceType, Type serviceType, bool transient = false) + { + ensure_not_built(); + + if (!can_register_service(serviceType)) + { + return; + } + + var multiServiceAttribute = interfaceType.GetCustomAttribute(); + + if (multiServiceAttribute != null && multiServiceAttribute.IsMultiService) + { + add_to_multi_services(interfaceType, serviceType); + } + else + { + validate_service_registration(interfaceType, serviceType, validate_multi_services: true); + remove_existing_registration(interfaceType); + + if (transient) + { + _transientServices.AddOrUpdate(interfaceType, serviceType, (key, value) => serviceType); + } + else + { + _singletonServices.AddOrUpdate(interfaceType, serviceType, (key, value) => serviceType); + } + } + } + + private IEnumerable remove_commands(Type commandType, string initialCommand) + { + var allCommandsForAttribute = commandType.GetCustomAttributes(typeof(CommandForAttribute), false).Cast(); + + foreach (var commandFor in allCommandsForAttribute) + { + var commandName = commandFor.CommandName.to_lower(); + if (_allCommands.Contains(commandName)) + { + _allCommands.Remove(commandName); + } + + Type tempType; + + if (!commandName.is_equal_to(initialCommand) && _registeredCommands.TryRemove(commandName, out tempType)) + { + yield return commandName; + } + } + } + + private void remove_existing_registration(Type interfaceType) + { + Type tempType; + Func tempAction; + _transientServices.TryRemove(interfaceType, out tempType); + _singletonServices.TryRemove(interfaceType, out tempType); + _instanceActionRegistrations.TryRemove(interfaceType, out tempAction); + } + + private void validate_commands_replacement(IEnumerable commandTypeAttributes) + { + validate_replace_permissions(); + + foreach (var commandFor in commandTypeAttributes) + { + var commandName = commandFor.CommandName.to_lower(); + + if (_allCommands.Contains(commandName)) + { + throw new ApplicationException("The command '{0}' is already registered for a different command handler!".format_with(commandName)); + } + } + } + + private void validate_replace_permissions() + { + if (!CanReplaceRegister) + { + throw new ApplicationException("{0} tried to replace an existing command without permission!"); + } + } + + private void validate_service_registration(Type interfaceType, Type serviceType, bool validate_multi_services) + { + if (interfaceType == typeof(IContainerRegistrator) || + serviceType.GetInterfaces().Contains(typeof(IContainerRegistrator))) + { + throw new ApplicationException("Registering a new container registrator is not allowed!"); + } + + var valid = serviceType.GetInterfaces().Contains(interfaceType) + || serviceType == interfaceType; + var typeCheck = serviceType.BaseType; + + while (!valid && interfaceType.IsClass && typeCheck != null) + { + if (typeCheck == interfaceType) + { + valid = true; + } + + typeCheck = serviceType.BaseType; + } + + if (!valid) + { + throw new ApplicationException("The type '{0}' is not inheriting from '{1}'. Unable to continue the registration.".format_with( + serviceType.Name, + interfaceType.Name)); + } + + if (_transientServices.ContainsKey(interfaceType) || + _singletonServices.ContainsKey(interfaceType) || + _instanceActionRegistrations.ContainsKey(interfaceType) || + (validate_multi_services && _multiServices.ContainsKey(interfaceType))) + { + validate_replace_permissions(); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/registration/SimpleInjectorContainerResolver.cs b/src/chocolatey/infrastructure.app/registration/SimpleInjectorContainerResolver.cs new file mode 100644 index 0000000000..cff173343b --- /dev/null +++ b/src/chocolatey/infrastructure.app/registration/SimpleInjectorContainerResolver.cs @@ -0,0 +1,43 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.registration +{ + using System.Collections.Generic; + using SimpleInjector; + + internal class SimpleInjectorContainerResolver : IContainerResolver + { + private readonly Container _container; + + public SimpleInjectorContainerResolver(Container container) + { + _container = container; + } + + public TService resolve() + where TService : class + { + return _container.GetInstance(); + } + + public IEnumerable resolve_all() + where TService : class + { + return _container.GetAllInstances(); + } + } +} diff --git a/src/chocolatey/infrastructure.app/runners/GenericRunner.cs b/src/chocolatey/infrastructure.app/runners/GenericRunner.cs index f9c1cf5b1b..769d4d78fc 100644 --- a/src/chocolatey/infrastructure.app/runners/GenericRunner.cs +++ b/src/chocolatey/infrastructure.app/runners/GenericRunner.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ namespace chocolatey.infrastructure.app.runners using System; using System.Linq; using System.Collections.Generic; + using chocolatey.infrastructure.app.services; using events; using filesystem; using infrastructure.events; @@ -70,20 +71,19 @@ private ICommand find_command(ChocolateyConfiguration config, Container containe warn_when_admin_needs_elevation(config); } - set_source_type(config); + set_source_type(config, container); // guaranteed that all settings are set. EnvironmentSettings.set_environment_variables(config); this.Log().Debug(() => "Configuration: {0}".format_with(config.ToString())); - if (isConsole && (config.HelpRequested || config.UnsuccessfulParsing)) { #if DEBUG Console.WriteLine("Press enter to continue..."); Console.ReadKey(); #endif - Environment.Exit(config.UnsuccessfulParsing? 1 : 0); + Environment.Exit(config.UnsuccessfulParsing ? 1 : 0); } var token = Assembly.GetExecutingAssembly().get_public_key_token(); @@ -104,7 +104,6 @@ Chocolatey is not an official build (bypassed with --allow-unofficial). now be in a bad state. Only official builds are to be trusted. " ); - } } } @@ -112,13 +111,20 @@ now be in a bad state. Only official builds are to be trusted. return command; } - private void set_source_type(ChocolateyConfiguration config) + private void set_source_type(ChocolateyConfiguration config, Container container) { - var sourceType = SourceType.normal; - Enum.TryParse(config.Sources, true, out sourceType); + var sourceRunner = container.GetAllInstances() + .FirstOrDefault(s => s.SourceType.is_equal_to(config.Sources) || s.SourceType.is_equal_to(config.Sources + "s")); + + var sourceType = SourceTypes.NORMAL; + if (sourceRunner != null) + { + sourceType = sourceRunner.SourceType; + } + config.SourceType = sourceType; - this.Log().Debug(() => "The source '{0}' evaluated to a '{1}' source type".format_with(config.Sources, sourceType.to_string())); + this.Log().Debug(() => "The source '{0}' evaluated to a '{1}' source type".format_with(config.Sources, sourceType)); } public void fail_when_license_is_missing_or_invalid_if_requested(ChocolateyConfiguration config) @@ -140,7 +146,7 @@ public void run(ChocolateyConfiguration config, Container container, bool isCons } fail_when_license_is_missing_or_invalid_if_requested(config); - SecurityProtocol.set_protocol(config, provideWarning:true); + SecurityProtocol.set_protocol(config, provideWarning: true); EventManager.publish(new PreRunMessage(config)); try @@ -201,7 +207,8 @@ private void remove_nuget_cache(Container container, ChocolateyConfiguration con var nugetX = fileSystem.combine_paths(fileSystem.get_temp_path(), "x", "nuget"); fileSystem.delete_directory_if_exists(nugetX, recursive: true, overrideAttributes: true, isSilent: true); - if (config != null && !string.IsNullOrWhiteSpace(config.CacheLocation)) { + if (config != null && !string.IsNullOrWhiteSpace(config.CacheLocation)) + { scratch = fileSystem.combine_paths(config.CacheLocation, "NuGetScratch"); fileSystem.delete_directory_if_exists(scratch, recursive: true, overrideAttributes: true, isSilent: true); nugetX = fileSystem.combine_paths(config.CacheLocation, "x", "nuget"); @@ -324,7 +331,5 @@ location. See } } } - } } - diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs index 07a1e7706a..9971e24fea 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs @@ -17,6 +17,7 @@ namespace chocolatey.infrastructure.app.services { using System; + using System.Collections.Generic; using System.IO; using System.Text; using configuration; @@ -42,6 +43,10 @@ public class ChocolateyPackageInformationService : IChocolateyPackageInformation private const string EXTRA_FILE = ".extra"; private const string VERSION_OVERRIDE_FILE = ".version"; + // We need to store the package identifiers we have warned about + // to prevent duplicated outputs. + private HashSet _deprecationWarning = new HashSet(); + public ChocolateyPackageInformationService(IFileSystem fileSystem, IRegistryService registryService, IFilesService filesService) { _fileSystem = fileSystem; @@ -141,6 +146,19 @@ has errored attempting to read it. This file will be renamed to var extraInfoFile = _fileSystem.combine_paths(pkgStorePath, EXTRA_FILE); if (_fileSystem.file_exists(extraInfoFile)) packageInformation.ExtraInformation = _fileSystem.read_file(extraInfoFile); + if (packageInformation.IsSideBySide && !_deprecationWarning.Contains(package.Id)) + { + var logger = _config.RegularOutput ? + logging.ChocolateyLoggers.Important : + logging.ChocolateyLoggers.LogFileOnly; + + this.Log().Warn(logger, @" +{0} has been installed as a side by side installation. +Side by side installations are deprecated and is pending removal in v2.0.0.", package.Id); + + _deprecationWarning.Add(package.Id); + } + var versionOverrideFile = _fileSystem.combine_paths(pkgStorePath, VERSION_OVERRIDE_FILE); if (_fileSystem.file_exists(versionOverrideFile)) { diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index e773cef9cc..5933f324ab 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ namespace chocolatey.infrastructure.app.services using System.IO; using System.Linq; using System.Text; - using builders; using commandline; using configuration; using domain; @@ -31,8 +30,8 @@ namespace chocolatey.infrastructure.app.services using infrastructure.events; using infrastructure.services; using logging; - using NuGet; using nuget; + using NuGet; using platforms; using results; using tolerance; @@ -91,6 +90,7 @@ system admins into something amazing! Singlehandedly solve your organization's struggles with software management and save the day! https://chocolatey.org/compare" }; + private const string PRO_BUSINESS_LIST_MESSAGE = @" Did you know Pro / Business automatically syncs with Programs and Features? Learn more about Package Synchronizer at @@ -135,26 +135,26 @@ public virtual int count_run(ChocolateyConfiguration config) private void perform_source_runner_action(ChocolateyConfiguration config, Action action) { - var runner = _sourceRunners.FirstOrDefault(r => r.SourceType == config.SourceType); + var runner = get_source_runner(config.SourceType); if (runner != null && action != null) { action.Invoke(runner); } else { - this.Log().Warn("No runner was found that implements source type '{0}' or action was missing".format_with(config.SourceType.to_string())); + this.Log().Warn("No runner was found that implements source type '{0}' or action was missing".format_with(config.SourceType)); } } private T perform_source_runner_function(ChocolateyConfiguration config, Func function) { - var runner = _sourceRunners.FirstOrDefault(r => r.SourceType == config.SourceType); + var runner = get_source_runner(config.SourceType); if (runner != null && function != null) { return function.Invoke(runner); } - this.Log().Warn("No runner was found that implements source type '{0}' or function was missing.".format_with(config.SourceType.to_string())); + this.Log().Warn("No runner was found that implements source type '{0}' or function was missing.".format_with(config.SourceType)); return default(T); } @@ -180,7 +180,7 @@ public virtual IEnumerable list_run(ChocolateyConfiguration confi foreach (var package in perform_source_runner_function(config, r => r.list_run(config))) { - if (config.SourceType == SourceType.normal) + if (config.SourceType.is_equal_to(SourceTypes.NORMAL)) { if (!config.ListCommand.IncludeRegistryPrograms) { @@ -239,7 +239,7 @@ private IEnumerable report_registry_programs(ChocolateyConfigurat public void pack_noop(ChocolateyConfiguration config) { - if (config.SourceType != SourceType.normal) + if (!config.SourceType.is_equal_to(SourceTypes.NORMAL)) { this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for packaging."); return; @@ -251,7 +251,7 @@ public void pack_noop(ChocolateyConfiguration config) public virtual void pack_run(ChocolateyConfiguration config) { - if (config.SourceType != SourceType.normal) + if (!config.SourceType.is_equal_to(SourceTypes.NORMAL)) { this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for packaging."); return; @@ -263,7 +263,7 @@ public virtual void pack_run(ChocolateyConfiguration config) public void push_noop(ChocolateyConfiguration config) { - if (config.SourceType != SourceType.normal) + if (!config.SourceType.is_equal_to(SourceTypes.NORMAL)) { this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for pushing."); return; @@ -275,7 +275,7 @@ public void push_noop(ChocolateyConfiguration config) public virtual void push_run(ChocolateyConfiguration config) { - if (config.SourceType != SourceType.normal) + if (!config.SourceType.is_equal_to(SourceTypes.NORMAL)) { this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for pushing."); return; @@ -291,7 +291,7 @@ public void install_noop(ChocolateyConfiguration config) foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, new ConcurrentDictionary()).or_empty_list_if_null()) { Action action = null; - if (packageConfig.SourceType == SourceType.normal) + if (packageConfig.SourceType.is_equal_to(SourceTypes.NORMAL)) { action = (pkg) => _powershellService.install_noop(pkg); } @@ -322,7 +322,7 @@ public void randomly_notify_about_pro_business(ChocolateyConfiguration config, s // doesn't like to grab the max value. var messageCount = _proBusinessMessages.Count; var chosenMessage = new Random().Next(0, messageCount); - if (chosenMessage >= messageCount) chosenMessage = messageCount -1; + if (chosenMessage >= messageCount) chosenMessage = messageCount - 1; message = _proBusinessMessages[chosenMessage]; } @@ -407,7 +407,9 @@ public virtual void handle_package_result(PackageResult packageResult, Chocolate { handle_extension_packages(config, packageResult); handle_template_packages(config, packageResult); + handle_hook_packages(config, packageResult); pkgInfo.Arguments = capture_arguments(config, packageResult); + pkgInfo.IsPinned = config.PinPackage; } var toolsLocation = Environment.GetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyToolsLocation); @@ -437,9 +439,9 @@ public virtual void handle_package_result(PackageResult packageResult, Chocolate remove_pending(packageResult, config); - if(_rebootExitCodes.Contains(packageResult.ExitCode)) + if (_rebootExitCodes.Contains(packageResult.ExitCode)) { - if(config.Features.ExitOnRebootDetected) + if (config.Features.ExitOnRebootDetected) { Environment.ExitCode = ApplicationParameters.ExitCodes.ErrorInstallSuspend; this.Log().Warn(ChocolateyLoggers.Important, @"Chocolatey has detected a pending reboot after installing/upgrading @@ -470,7 +472,7 @@ public virtual void handle_package_result(PackageResult packageResult, Chocolate var installerDetected = Environment.GetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyPackageInstallerType); if (!string.IsNullOrWhiteSpace(installLocation)) { - this.Log().Info(ChocolateyLoggers.Important, " Software installed to '{0}'".format_with(installLocation.escape_curly_braces())); + this.Log().Info(ChocolateyLoggers.Important, " Software installed to '{0}'".format_with(installLocation.escape_curly_braces())); } else if (!string.IsNullOrWhiteSpace(installerDetected)) { @@ -572,6 +574,11 @@ private string capture_arguments(ChocolateyConfiguration config, PackageResult p public virtual ConcurrentDictionary install_run(ChocolateyConfiguration config) { + if (config.AllowMultipleVersions) + { + this.Log().Warn(ChocolateyLoggers.Important, "Installing the same package with multiple versions is deprecated and will be removed in v2.0.0."); + } + this.Log().Info(is_packages_config_file(config.PackageNames) ? @"Installing from config file:" : @"Installing the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); @@ -594,7 +601,7 @@ public virtual ConcurrentDictionary install_run(Chocolate foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null()) { Action action = null; - if (packageConfig.SourceType == SourceType.normal) + if (packageConfig.SourceType.is_equal_to(SourceTypes.NORMAL)) { action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); } @@ -630,7 +637,7 @@ Would have determined packages that are out of date based on what is public virtual void outdated_run(ChocolateyConfiguration config) { - if (config.SourceType != SourceType.normal) + if (!config.SourceType.is_equal_to(SourceTypes.NORMAL)) { this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for outdated."); return; @@ -687,7 +694,7 @@ private IEnumerable set_config_from_package_names_and_p foreach (var packageConfig in get_packages_from_config(packageConfigFile, config, packageInstalls).or_empty_list_if_null()) { - yield return packageConfig; + yield return packageConfig; } } @@ -735,11 +742,58 @@ private IEnumerable get_packages_from_config(string pac if (pkgSettings.IgnoreDependencies) packageConfig.IgnoreDependencies = true; if (pkgSettings.ApplyInstallArgumentsToDependencies) packageConfig.ApplyInstallArgumentsToDependencies = true; if (pkgSettings.ApplyPackageParametersToDependencies) packageConfig.ApplyPackageParametersToDependencies = true; - SourceType sourceType; - if (Enum.TryParse(pkgSettings.Source, true, out sourceType)) packageConfig.SourceType = sourceType; + + if (!string.IsNullOrWhiteSpace(pkgSettings.Source) && has_source_type(pkgSettings.Source)) + { + packageConfig.SourceType = pkgSettings.Source; + } + + if (pkgSettings.PinPackage) packageConfig.PinPackage = true; + if (pkgSettings.Force) packageConfig.Force = true; + packageConfig.CommandExecutionTimeoutSeconds = pkgSettings.ExecutionTimeout == -1 ? packageConfig.CommandExecutionTimeoutSeconds : pkgSettings.ExecutionTimeout; + if (pkgSettings.Prerelease) packageConfig.Prerelease = true; + if (pkgSettings.OverrideArguments) packageConfig.OverrideArguments = true; + if (pkgSettings.NotSilent) packageConfig.NotSilent = true; + if (pkgSettings.AllowDowngrade) packageConfig.AllowDowngrade = true; + if (pkgSettings.ForceDependencies) packageConfig.ForceDependencies = true; + if (pkgSettings.SkipAutomationScripts) packageConfig.SkipPackageInstallProvider = true; + packageConfig.SourceCommand.Username = string.IsNullOrWhiteSpace(pkgSettings.User) ? packageConfig.SourceCommand.Username : pkgSettings.User; + packageConfig.SourceCommand.Password = string.IsNullOrWhiteSpace(pkgSettings.Password) ? packageConfig.SourceCommand.Password : pkgSettings.Password; + packageConfig.SourceCommand.Certificate = string.IsNullOrWhiteSpace(pkgSettings.Cert) ? packageConfig.SourceCommand.Certificate : pkgSettings.Cert; + packageConfig.SourceCommand.CertificatePassword = string.IsNullOrWhiteSpace(pkgSettings.CertPassword) ? packageConfig.SourceCommand.CertificatePassword : pkgSettings.CertPassword; + if (pkgSettings.IgnoreChecksums) packageConfig.Features.ChecksumFiles = false; + if (pkgSettings.AllowEmptyChecksums) packageConfig.Features.AllowEmptyChecksums = true; + if (pkgSettings.AllowEmptyChecksumsSecure) packageConfig.Features.AllowEmptyChecksumsSecure = true; + if (pkgSettings.RequireChecksums) + { + packageConfig.Features.AllowEmptyChecksums = false; + packageConfig.Features.AllowEmptyChecksumsSecure = false; + } + packageConfig.DownloadChecksum = string.IsNullOrWhiteSpace(pkgSettings.DownloadChecksum) ? packageConfig.DownloadChecksum : pkgSettings.DownloadChecksum; + packageConfig.DownloadChecksum64 = string.IsNullOrWhiteSpace(pkgSettings.DownloadChecksum64) ? packageConfig.DownloadChecksum64 : pkgSettings.DownloadChecksum64; + packageConfig.DownloadChecksumType = string.IsNullOrWhiteSpace(pkgSettings.DownloadChecksumType) ? packageConfig.DownloadChecksumType : pkgSettings.DownloadChecksumType; + packageConfig.DownloadChecksumType64 = string.IsNullOrWhiteSpace(pkgSettings.DownloadChecksumType64) ? packageConfig.DownloadChecksumType : pkgSettings.DownloadChecksumType64; + if (pkgSettings.IgnorePackageExitCodes) packageConfig.Features.UsePackageExitCodes = false; + if (pkgSettings.UsePackageExitCodes) packageConfig.Features.UsePackageExitCodes = true; + if (pkgSettings.StopOnFirstFailure) packageConfig.Features.StopOnFirstPackageFailure = true; + if (pkgSettings.ExitWhenRebootDetected) packageConfig.Features.ExitOnRebootDetected = true; + if (pkgSettings.IgnoreDetectedReboot) packageConfig.Features.ExitOnRebootDetected = false; + if (pkgSettings.DisableRepositoryOptimizations) packageConfig.Features.UsePackageRepositoryOptimizations = false; + if (pkgSettings.AcceptLicense) packageConfig.AcceptLicense = true; + if (pkgSettings.Confirm) + { + packageConfig.PromptForConfirmation = false; + packageConfig.AcceptLicense = true; + } + if (pkgSettings.LimitOutput) packageConfig.RegularOutput = false; + packageConfig.CacheLocation = string.IsNullOrWhiteSpace(pkgSettings.CacheLocation) ? packageConfig.CacheLocation : pkgSettings.CacheLocation; + if (pkgSettings.FailOnStderr) packageConfig.Features.FailOnStandardError = true; + if (pkgSettings.UseSystemPowershell) packageConfig.Features.UsePowerShellHost = false; + if (pkgSettings.NoProgress) packageConfig.Features.ShowDownloadProgress = false; this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(packageConfig.PackageNames)); packageConfigs.Add(packageConfig); + this.Log().Debug(() => "Package Configuration: {0}".format_with(packageConfig.ToString())); } } @@ -749,7 +803,7 @@ private IEnumerable get_packages_from_config(string pac public void upgrade_noop(ChocolateyConfiguration config) { Action action = null; - if (config.SourceType == SourceType.normal) + if (config.SourceType.is_equal_to(SourceTypes.NORMAL)) { action = (pkg) => _powershellService.install_noop(pkg); } @@ -765,6 +819,11 @@ public void upgrade_noop(ChocolateyConfiguration config) public virtual ConcurrentDictionary upgrade_run(ChocolateyConfiguration config) { + if (config.AllowMultipleVersions) + { + this.Log().Warn(ChocolateyLoggers.Important, "Upgrading the same package with multiple versions is deprecated and will be removed in v2.0.0."); + } + this.Log().Info(@"Upgrading the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); @@ -788,7 +847,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate try { Action action = null; - if (config.SourceType == SourceType.normal) + if (config.SourceType.is_equal_to(SourceTypes.NORMAL)) { action = (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade); } @@ -832,7 +891,7 @@ private void before_package_modify(PackageResult packageResult, ChocolateyConfig public void uninstall_noop(ChocolateyConfiguration config) { Action action = null; - if (config.SourceType == SourceType.normal) + if (config.SourceType.is_equal_to(SourceTypes.NORMAL)) { action = (pkg) => { @@ -860,7 +919,7 @@ public virtual ConcurrentDictionary uninstall_run(Chocola try { Action action = null; - if (config.SourceType == SourceType.normal) + if (config.SourceType.is_equal_to(SourceTypes.NORMAL)) { action = (packageResult) => handle_package_uninstall(packageResult, config); } @@ -926,7 +985,7 @@ private int report_action_summary(ConcurrentDictionary pa actionName, successes.Count(), packageResults.Count, - (failures > 0) ? failures + " packages failed.": string.Empty, + (failures > 0) ? failures + " packages failed." : string.Empty, _fileSystem.combine_paths(ApplicationParameters.LoggingLocation, ApplicationParameters.LoggingFile) )); @@ -934,7 +993,7 @@ private int report_action_summary(ConcurrentDictionary pa if (packageResults.Count >= 5 && successes.Count() != 0) { this.Log().Info(""); - this.Log().Warn("{0}{1}:".format_with(actionName.Substring(0,1).ToUpper(), actionName.Substring(1))); + this.Log().Warn("{0}{1}:".format_with(actionName.Substring(0, 1).ToUpper(), actionName.Substring(1))); foreach (var packageResult in successes.or_empty_list_if_null()) { this.Log().Info(" - {0} v{1}".format_with(packageResult.Value.Name, packageResult.Value.Version)); @@ -1025,9 +1084,9 @@ public virtual void handle_package_uninstall(PackageResult packageResult, Chocol handle_unsuccessful_operation(config, packageResult, movePackageToFailureLocation: false, attemptRollback: false); } - if(_rebootExitCodes.Contains(packageResult.ExitCode)) + if (_rebootExitCodes.Contains(packageResult.ExitCode)) { - if(config.Features.ExitOnRebootDetected) + if (config.Features.ExitOnRebootDetected) { Environment.ExitCode = ApplicationParameters.ExitCodes.ErrorInstallSuspend; this.Log().Warn(ChocolateyLoggers.Important, @"Chocolatey has detected a pending reboot after uninstalling @@ -1052,6 +1111,7 @@ private void uninstall_cleanup(ChocolateyConfiguration config, PackageResult pac remove_rollback_if_exists(packageResult); handle_extension_packages(config, packageResult); handle_template_packages(config, packageResult); + handle_hook_packages(config, packageResult); if (config.Force) { @@ -1154,7 +1214,6 @@ private void remove_extension_folder(string packageExtensionsDirectory) "Attempted to remove '{0}' but had an error".format_with(packageExtensionsDirectory), throwError: false, logWarningInsteadOfError: true); - } private void handle_template_packages(ChocolateyConfiguration config, PackageResult packageResult) @@ -1252,6 +1311,11 @@ rollback the previous version. Erroneous install location captured as } } + private bool has_source_type(string source) + { + return _sourceRunners.Any(s => s.SourceType == source || s.SourceType == source + "s"); + } + private void move_bad_package_to_failure_location(PackageResult packageResult) { _fileSystem.create_directory_if_not_exists(ApplicationParameters.PackageFailuresLocation); @@ -1449,5 +1513,57 @@ private void get_log_environment_changes(ChocolateyConfiguration config, IEnumer } } } + + private ISourceRunner get_source_runner(string sourceType) + { + // We add the trailing s to the check in case of windows feature which can be specified both with and without + // the s. + return _sourceRunners.FirstOrDefault(s => s.SourceType == sourceType || s.SourceType == sourceType + "s"); + } + + private void handle_hook_packages(ChocolateyConfiguration config, PackageResult packageResult) + { + if (packageResult == null) return; + if (!packageResult.Name.to_lower().EndsWith(ApplicationParameters.HookPackageIdExtension)) return; + + _fileSystem.create_directory_if_not_exists(ApplicationParameters.HooksLocation); + var hookFolderName = packageResult.Name.to_lower().Replace(ApplicationParameters.HookPackageIdExtension, string.Empty); + var installHookPath = _fileSystem.combine_paths(ApplicationParameters.HooksLocation, hookFolderName); + + FaultTolerance.try_catch_with_logging_exception( + () => + { + _fileSystem.delete_directory_if_exists(installHookPath, recursive: true); + }, + "Attempted to remove '{0}' but had an error".format_with(installHookPath)); + + if (!config.CommandName.is_equal_to(CommandNameType.uninstall.to_string())) + { + if (packageResult.InstallLocation == null) return; + + _fileSystem.create_directory_if_not_exists(installHookPath); + var hookPath = _fileSystem.combine_paths(packageResult.InstallLocation, "hook"); + var hookFolderToCopy = _fileSystem.directory_exists(hookPath) ? hookPath : packageResult.InstallLocation; + + FaultTolerance.try_catch_with_logging_exception( + () => + { + _fileSystem.copy_directory(hookFolderToCopy, installHookPath, overwriteExisting: true); + }, + "Attempted to copy{0} '{1}'{0} to '{2}'{0} but had an error".format_with(Environment.NewLine, hookFolderToCopy, installHookPath)); + + string logMessage = " Installed/updated {0} hook.".format_with(hookFolderName); + this.Log().Warn(logMessage); + packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); + + Environment.SetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyPackageInstallLocation, installHookPath, EnvironmentVariableTarget.Process); + } + else + { + string logMessage = " Uninstalled {0} hook.".format_with(hookFolderName); + this.Log().Warn(logMessage); + packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); + } + } } } diff --git a/src/chocolatey/infrastructure.app/services/CygwinService.cs b/src/chocolatey/infrastructure.app/services/CygwinService.cs index d3d6fb3ef7..2557d502d2 100644 --- a/src/chocolatey/infrastructure.app/services/CygwinService.cs +++ b/src/chocolatey/infrastructure.app/services/CygwinService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,44 +85,44 @@ private void set_install_dictionary(IDictionary // UseValueOnly = true, // Required = true //}); - args.Add("_quiet_", new ExternalCommandArgument {ArgumentOption = "--quiet-mode", Required = true}); - args.Add("_no_desktop_", new ExternalCommandArgument {ArgumentOption = "--no-desktop", Required = true}); - args.Add("_no_startmenu_", new ExternalCommandArgument {ArgumentOption = "--no-startmenu", Required = true}); + args.Add("_quiet_", new ExternalCommandArgument { ArgumentOption = "--quiet-mode", Required = true }); + args.Add("_no_desktop_", new ExternalCommandArgument { ArgumentOption = "--no-desktop", Required = true }); + args.Add("_no_startmenu_", new ExternalCommandArgument { ArgumentOption = "--no-startmenu", Required = true }); args.Add("_root_", new ExternalCommandArgument - { - ArgumentOption = "--root ", - ArgumentValue = INSTALL_ROOT_TOKEN, - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "--root ", + ArgumentValue = INSTALL_ROOT_TOKEN, + QuoteValue = false, + Required = true + }); args.Add("_local_pkgs_dir_", new ExternalCommandArgument - { - ArgumentOption = "--local-package-dir ", - ArgumentValue = "{0}\\packages".format_with(INSTALL_ROOT_TOKEN), - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "--local-package-dir ", + ArgumentValue = "{0}\\packages".format_with(INSTALL_ROOT_TOKEN), + QuoteValue = false, + Required = true + }); args.Add("_site_", new ExternalCommandArgument - { - ArgumentOption = "--site ", - ArgumentValue = "http://mirrors.kernel.org/sourceware/cygwin/", - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "--site ", + ArgumentValue = "http://mirrors.kernel.org/sourceware/cygwin/", + QuoteValue = false, + Required = true + }); args.Add("_package_name_", new ExternalCommandArgument - { - ArgumentOption = "--packages ", - ArgumentValue = PACKAGE_NAME_TOKEN, - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "--packages ", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); } - public SourceType SourceType + public string SourceType { - get { return SourceType.cygwin; } + get { return SourceTypes.CYGWIN; } } public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) @@ -130,18 +130,18 @@ public void ensure_source_app_installed(ChocolateyConfiguration config, Action

additionalActionsBeforeScript); + + PowerShellExecutionResults run_host(ChocolateyConfiguration config, string chocoPowerShellScript, Action additionalActionsBeforeScript, IEnumerable hookPreScriptPathList, IEnumerable hookPostScriptPathList); } } diff --git a/src/chocolatey/infrastructure.app/services/ISourceRunner.cs b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs index 1950bda94a..3a1aaac649 100644 --- a/src/chocolatey/infrastructure.app/services/ISourceRunner.cs +++ b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,12 @@ namespace chocolatey.infrastructure.app.services using System; using System.Collections.Concurrent; using System.Collections.Generic; + using chocolatey.infrastructure.app.attributes; using configuration; using domain; using results; + [MultiService] public interface ISourceRunner { ///

@@ -31,7 +33,7 @@ public interface ISourceRunner /// /// The type of the source. /// - SourceType SourceType { get; } + string SourceType { get; } /// /// Ensures the application that controls a source is installed diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 97085b77b0..651cd84e68 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,9 +71,9 @@ public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPack _packageDownloader = packageDownloader; } - public SourceType SourceType + public string SourceType { - get { return SourceType.normal; } + get { return SourceTypes.NORMAL; } } public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) @@ -328,7 +328,6 @@ public virtual void push_run(ChocolateyConfiguration config) NugetPush.push_package(config, _fileSystem.get_full_path(nupkgFilePath)); - if (config.RegularOutput && (config.Sources.is_equal_to(ApplicationParameters.ChocolateyCommunityFeedPushSource) || config.Sources.is_equal_to(ApplicationParameters.ChocolateyCommunityFeedPushSourceOld))) { this.Log().Warn(ChocolateyLoggers.Important, () => @" @@ -436,13 +435,13 @@ public virtual ConcurrentDictionary install_run(Chocolate uninstallSuccessAction: null, addUninstallHandler: true); - var originalConfig = config.deep_copy(); + config.start_backup(); foreach (string packageName in packageNames.or_empty_list_if_null()) { - // reset config each time through - config = originalConfig.deep_copy(); - + // We need to ensure we are using a clean configuration file + // before we start reading it. + config.reset_config(); //todo: #2577 get smarter about realizing multiple versions have been installed before and allowing that IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName); @@ -555,6 +554,11 @@ Version was specified as '{0}'. It is possible that version } } + // Reset the configuration again once we are completely done with the processing of + // configurations, and make sure that we are removing any backup that was created + // as part of this run. + config.reset_config(removeBackup: true); + return packageInstalls; } @@ -617,23 +621,17 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate uninstallSuccessAction: null, addUninstallHandler: false); - var localRepository = packageManager.LocalRepository as ChocolateyLocalPackageRepository; - - if (localRepository != null) - { - localRepository.IgnoreVersionedDirectories = !config.AllowMultipleVersions; - } - var configIgnoreDependencies = config.IgnoreDependencies; set_package_names_if_all_is_specified(config, () => { config.IgnoreDependencies = true; }); config.IgnoreDependencies = configIgnoreDependencies; - var originalConfig = config.deep_copy(); + config.start_backup(); foreach (string packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null()) { - // reset config each time through - config = originalConfig.deep_copy(); + // We need to ensure we are using a clean configuration file + // before we start reading it. + config.reset_config(); IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName); @@ -885,6 +883,11 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate } } + // Reset the configuration again once we are completely done with the processing of + // configurations, and make sure that we are removing any backup that was created + // as part of this run. + config.reset_config(removeBackup: true); + return packageInstalls; } @@ -904,12 +907,13 @@ public virtual ConcurrentDictionary get_outdated(Chocolat set_package_names_if_all_is_specified(config, () => { config.IgnoreDependencies = true; }); var packageNames = config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null().ToList(); - var originalConfig = config.deep_copy(); + config.start_backup(); foreach (var packageName in packageNames) { - // reset config each time through - config = originalConfig.deep_copy(); + // We need to ensure we are using a clean configuration file + // before we start reading it. + config.reset_config(); var installedPackage = packageManager.LocalRepository.FindPackage(packageName); var pkgInfo = _packageInfoService.get_package_information(installedPackage); @@ -957,8 +961,22 @@ public virtual ConcurrentDictionary get_outdated(Chocolat packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); this.Log().Info("{0}|{1}|{2}|{3}".format_with(installedPackage.Id, installedPackage.Version, latestPackage.Version, isPinned.to_string().to_lower())); + + if (pkgInfo.IsSideBySide) + { + var deprecationMessage = @" +{0} v{1} has been installed as a side by side installation. +Side by side installations are deprecated and is pending removal in v2.0.0".format_with(installedPackage.Id, installedPackage.Version); + + packageResult.Messages.Add(new ResultMessage(ResultType.Warn, deprecationMessage)); + } } + // Reset the configuration again once we are completely done with the processing of + // configurations, and make sure that we are removing any backup that was created + // as part of this run. + config.reset_config(removeBackup: true); + return outdatedPackages; } @@ -1232,7 +1250,6 @@ private void remove_nuget_cache_for_package(IPackage installedPackage) var nugetCachedFile = _fileSystem.combine_paths(localAppData, "NuGet", "Cache", "{0}.{1}.nupkg".format_with(installedPackage.Id, installedPackage.Version.to_string())); if (_fileSystem.file_exists(nugetCachedFile)) { - _fileSystem.delete_file(nugetCachedFile); } }, @@ -1273,6 +1290,8 @@ public virtual ConcurrentDictionary uninstall_run(Chocola { var pkg = e.Package; + // TODO: Removal special handling for SxS packages once we hit v2.0.0 + // this section fires twice sometimes, like for older packages in a sxs install... var packageResult = packageUninstalls.GetOrAdd(pkg.Id.to_lower() + "." + pkg.Version.to_string(), new PackageResult(pkg, e.InstallPath)); packageResult.InstallLocation = e.InstallPath; @@ -1371,12 +1390,13 @@ public virtual ConcurrentDictionary uninstall_run(Chocola config.ForceDependencies = false; }); - var originalConfig = config.deep_copy(); + config.start_backup(); foreach (string packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null()) { - // reset config each time through - config = originalConfig.deep_copy(); + // We need to ensure we are using a clean configuration file + // before we start reading it. + config.reset_config(); IList installedPackageVersions = new List(); if (string.IsNullOrWhiteSpace(config.Version)) @@ -1504,6 +1524,11 @@ public virtual ConcurrentDictionary uninstall_run(Chocola } } + // Reset the configuration again once we are completely done with the processing of + // configurations, and make sure that we are removing any backup that was created + // as part of this run. + config.reset_config(removeBackup: true); + return packageUninstalls; } diff --git a/src/chocolatey/infrastructure.app/services/PowershellService.cs b/src/chocolatey/infrastructure.app/services/PowershellService.cs index 0faf6952d3..6f1be64f55 100644 --- a/src/chocolatey/infrastructure.app/services/PowershellService.cs +++ b/src/chocolatey/infrastructure.app/services/PowershellService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,10 +23,7 @@ namespace chocolatey.infrastructure.app.services using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Reflection; - using System.Security.Cryptography; - using System.Text; using adapters; - using builders; using commandline; using configuration; using cryptography; @@ -38,8 +35,6 @@ namespace chocolatey.infrastructure.app.services using powershell; using results; using utility; - using Assembly = adapters.Assembly; - using Console = System.Console; using CryptoHashProvider = cryptography.CryptoHashProvider; using Environment = System.Environment; using IFileSystem = filesystem.IFileSystem; @@ -89,6 +84,51 @@ private string get_script_for_action(PackageResult packageResult, CommandNameTyp return string.Empty; } + private IEnumerable get_hook_scripts(ChocolateyConfiguration configuration, PackageResult packageResult, CommandNameType command, bool isPreHook) + { + List hookScriptPaths = new List(); + + // If skipping hook scripts, return an empty list + if (configuration.SkipHookScripts) return hookScriptPaths; + + // If hooks directory doesn't exist, return an empty list to prevent directory not exist warnings + if (!_fileSystem.directory_exists(ApplicationParameters.HooksLocation)) return hookScriptPaths; + + string filenameBase; + + if (isPreHook) + { + filenameBase = "pre-"; + } + else + { + filenameBase = "post-"; + } + + switch (command) + { + case CommandNameType.install: + filenameBase += "install-"; + break; + + case CommandNameType.uninstall: + filenameBase += "uninstall-"; + break; + + case CommandNameType.upgrade: + filenameBase += "beforemodify-"; + break; + + default: + throw new ApplicationException("Could not find CommandNameType '{0}' to get hook scripts".format_with(command)); + } + + hookScriptPaths.AddRange(_fileSystem.get_files(ApplicationParameters.HooksLocation, "{0}all.ps1".format_with(filenameBase), SearchOption.AllDirectories)); + hookScriptPaths.AddRange(_fileSystem.get_files(ApplicationParameters.HooksLocation, "{0}{1}.ps1".format_with(filenameBase, packageResult.Name), SearchOption.AllDirectories)); + + return hookScriptPaths; + } + public void noop_action(PackageResult packageResult, CommandNameType command) { var chocoInstall = get_script_for_action(packageResult, command); @@ -134,7 +174,7 @@ private string get_helpers_folder() return _fileSystem.combine_paths(ApplicationParameters.InstallLocation, "helpers"); } - public string wrap_script_with_module(string script, ChocolateyConfiguration config) + public string wrap_script_with_module(string script, IEnumerable hookPreScriptPathList, IEnumerable hookPostScriptPathList, ChocolateyConfiguration config) { var installerModule = _fileSystem.combine_paths(get_helpers_folder(), "chocolateyInstaller.psm1"); var scriptRunner = _fileSystem.combine_paths(get_helpers_folder(), "chocolateyScriptRunner.ps1"); @@ -148,18 +188,20 @@ public string wrap_script_with_module(string script, ChocolateyConfiguration con installerModule, scriptRunner, string.IsNullOrWhiteSpace(_customImports) ? string.Empty : "& {0}".format_with(_customImports.EndsWith(";") ? _customImports : _customImports + ";"), - get_script_arguments(script, config) + get_script_arguments(script, hookPreScriptPathList, hookPostScriptPathList, config) ); } - private string get_script_arguments(string script, ChocolateyConfiguration config) + private string get_script_arguments(string script, IEnumerable hookPreScriptPathList, IEnumerable hookPostScriptPathList, ChocolateyConfiguration config) { - return "-packageScript '{0}' -installArguments '{1}' -packageParameters '{2}'{3}{4}".format_with( + return "-packageScript '{0}' -installArguments '{1}' -packageParameters '{2}'{3}{4} -preRunHookScripts {5} -postRunHookScripts {6}".format_with( script, prepare_powershell_arguments(config.InstallArguments), prepare_powershell_arguments(config.PackageParameters), config.ForceX86 ? " -forceX86" : string.Empty, - config.OverrideArguments ? " -overrideArgs" : string.Empty + config.OverrideArguments ? " -overrideArgs" : string.Empty, + hookPreScriptPathList.Any() ? "{0}".format_with(string.Join(",", hookPreScriptPathList)) : "$null", + hookPostScriptPathList.Any() ? "{0}".format_with(string.Join(",", hookPostScriptPathList)) : "$null" ); } @@ -192,62 +234,81 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack } var chocoPowerShellScript = get_script_for_action(packageResult, command); - if (!string.IsNullOrEmpty(chocoPowerShellScript)) + + var hookPreScriptPathList = get_hook_scripts(configuration, packageResult, command, true); + var hookPostScriptPathList = get_hook_scripts(configuration, packageResult, command, false); + + foreach (var hookScriptPath in hookPreScriptPathList.Concat(hookPostScriptPathList).or_empty_list_if_null()) + { + this.Log().Debug(ChocolateyLoggers.Important, "Contents of '{0}':".format_with(chocoPowerShellScript)); + string hookScriptContents = _fileSystem.read_file(hookScriptPath); + this.Log().Debug(() => hookScriptContents.escape_curly_braces()); + } + + if (!string.IsNullOrEmpty(chocoPowerShellScript) || hookPreScriptPathList.Any() || hookPostScriptPathList.Any()) { var failure = false; var package = packageResult.Package; prepare_powershell_environment(package, configuration, packageDirectory); - - this.Log().Debug(ChocolateyLoggers.Important, "Contents of '{0}':".format_with(chocoPowerShellScript)); - string chocoPowerShellScriptContents = _fileSystem.read_file(chocoPowerShellScript); - // leave this way, doesn't take it through formatting. - this.Log().Debug(() => chocoPowerShellScriptContents.escape_curly_braces()); - bool shouldRun = !configuration.PromptForConfirmation; - if (!shouldRun) + if (!string.IsNullOrEmpty(chocoPowerShellScript)) { - this.Log().Info(ChocolateyLoggers.Important, () => "The package {0} wants to run '{1}'.".format_with(package.Id, _fileSystem.get_file_name(chocoPowerShellScript))); - this.Log().Info(ChocolateyLoggers.Important, () => "Note: If you don't run this script, the installation will fail."); - this.Log().Info(ChocolateyLoggers.Important, () => @"Note: To confirm automatically next time, use '-y' or consider:"); - this.Log().Info(ChocolateyLoggers.Important, () => @"choco feature enable -n allowGlobalConfirmation"); - - var selection = InteractivePrompt.prompt_for_confirmation(@"Do you want to run the script?", - new[] { "yes", "all - yes to all", "no", "print" }, - defaultChoice: null, - requireAnswer: true, - allowShortAnswer: true, - shortPrompt: true - ); + this.Log().Debug(ChocolateyLoggers.Important, "Contents of '{0}':".format_with(chocoPowerShellScript)); + string chocoPowerShellScriptContents = _fileSystem.read_file(chocoPowerShellScript); + // leave this way, doesn't take it through formatting. + this.Log().Debug(() => chocoPowerShellScriptContents.escape_curly_braces()); - if (selection.is_equal_to("print")) + if (!shouldRun) { - this.Log().Info(ChocolateyLoggers.Important, "------ BEGIN SCRIPT ------"); - this.Log().Info(() => "{0}{1}{0}".format_with(Environment.NewLine, chocoPowerShellScriptContents.escape_curly_braces())); - this.Log().Info(ChocolateyLoggers.Important, "------- END SCRIPT -------"); - selection = InteractivePrompt.prompt_for_confirmation(@"Do you want to run this script?", - new[] { "yes", "no" }, + this.Log().Info(ChocolateyLoggers.Important, () => "The package {0} wants to run '{1}'.".format_with(package.Id, _fileSystem.get_file_name(chocoPowerShellScript))); + this.Log().Info(ChocolateyLoggers.Important, () => "Note: If you don't run this script, the installation will fail."); + this.Log().Info(ChocolateyLoggers.Important, () => @"Note: To confirm automatically next time, use '-y' or consider:"); + this.Log().Info(ChocolateyLoggers.Important, () => @"choco feature enable -n allowGlobalConfirmation"); + + var selection = InteractivePrompt.prompt_for_confirmation(@"Do you want to run the script?", + new[] { "yes", "all - yes to all", "no", "print" }, defaultChoice: null, requireAnswer: true, allowShortAnswer: true, shortPrompt: true + ); + + if (selection.is_equal_to("print")) + { + this.Log().Info(ChocolateyLoggers.Important, "------ BEGIN SCRIPT ------"); + this.Log().Info(() => "{0}{1}{0}".format_with(Environment.NewLine, chocoPowerShellScriptContents.escape_curly_braces())); + this.Log().Info(ChocolateyLoggers.Important, "------- END SCRIPT -------"); + selection = InteractivePrompt.prompt_for_confirmation(@"Do you want to run this script?", + new[] { "yes", "no" }, + defaultChoice: null, + requireAnswer: true, + allowShortAnswer: true, + shortPrompt: true ); - } + } - if (selection.is_equal_to("yes")) shouldRun = true; - if (selection.is_equal_to("all - yes to all")) - { - configuration.PromptForConfirmation = false; - shouldRun = true; - } - if (selection.is_equal_to("no")) - { - //MSI ERROR_INSTALL_USEREXIT - 1602 - https://support.microsoft.com/en-us/kb/304888 / https://msdn.microsoft.com/en-us/library/aa376931.aspx - //ERROR_INSTALL_CANCEL - 15608 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms681384.aspx - Environment.ExitCode = 15608; - packageResult.Messages.Add(new ResultMessage(ResultType.Error, "User canceled powershell portion of installation for '{0}'.{1} Specify -n to skip automated script actions.".format_with(chocoPowerShellScript, Environment.NewLine))); + if (selection.is_equal_to("yes")) shouldRun = true; + if (selection.is_equal_to("all - yes to all")) + { + configuration.PromptForConfirmation = false; + shouldRun = true; + } + + if (selection.is_equal_to("no")) + { + //MSI ERROR_INSTALL_USEREXIT - 1602 - https://support.microsoft.com/en-us/kb/304888 / https://msdn.microsoft.com/en-us/library/aa376931.aspx + //ERROR_INSTALL_CANCEL - 15608 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms681384.aspx + Environment.ExitCode = 15608; + packageResult.Messages.Add(new ResultMessage(ResultType.Error, "User canceled powershell portion of installation for '{0}'.{1} Specify -n to skip automated script actions.".format_with(chocoPowerShellScript, Environment.NewLine))); + } } } + else + { + shouldRun = true; + this.Log().Info("No package automation script, running only hooks", ChocolateyLoggers.Important); + } if (shouldRun) { @@ -266,8 +327,8 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack try { result = configuration.Features.UsePowerShellHost - ? Execute.with_timeout(configuration.CommandExecutionTimeoutSeconds).command(() => run_host(configuration, chocoPowerShellScript, null), result) - : run_external_powershell(configuration, chocoPowerShellScript); + ? Execute.with_timeout(configuration.CommandExecutionTimeoutSeconds).command(() => run_host(configuration, chocoPowerShellScript, null, hookPreScriptPathList, hookPostScriptPathList), result) + : run_external_powershell(configuration, chocoPowerShellScript, hookPreScriptPathList, hookPostScriptPathList); } catch (Exception ex) { @@ -293,7 +354,6 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack `choco -h` for details."); } - if (result.ExitCode != 0) { Environment.ExitCode = result.ExitCode; @@ -328,11 +388,11 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack return installerRun; } - private PowerShellExecutionResults run_external_powershell(ChocolateyConfiguration configuration, string chocoPowerShellScript) + private PowerShellExecutionResults run_external_powershell(ChocolateyConfiguration configuration, string chocoPowerShellScript, IEnumerable hookPreScriptPathList, IEnumerable hookPostScriptPathList) { var result = new PowerShellExecutionResults(); result.ExitCode = PowershellExecutor.execute( - wrap_script_with_module(chocoPowerShellScript, configuration), + wrap_script_with_module(chocoPowerShellScript, hookPreScriptPathList, hookPostScriptPathList, configuration), _fileSystem, configuration.CommandExecutionTimeoutSeconds, (s, e) => @@ -474,7 +534,7 @@ public void prepare_powershell_environment(IPackage package, ChocolateyConfigura } } - SecurityProtocol.set_protocol(configuration, provideWarning:false); + SecurityProtocol.set_protocol(configuration, provideWarning: false); } private ResolveEventHandler _handler = null; @@ -533,14 +593,21 @@ private void remove_assembly_resolver() } } - public PowerShellExecutionResults run_host(ChocolateyConfiguration config, string chocoPowerShellScript, Action additionalActionsBeforeScript) + [Obsolete("This version of running the powershell host do not support running additional hooks. Use the appropriate overload instead")] + public PowerShellExecutionResults run_host(ChocolateyConfiguration config, string chocoPowershellScript, Action additionalActionsBeforeScript) + { + return run_host(config, chocoPowershellScript, additionalActionsBeforeScript, Enumerable.Empty(), Enumerable.Empty()); + } + + public PowerShellExecutionResults run_host(ChocolateyConfiguration config, string chocoPowerShellScript, Action additionalActionsBeforeScript, IEnumerable hookPreScriptPathList, IEnumerable hookPostScriptPathList) { // since we control output in the host, always set these true Environment.SetEnvironmentVariable("ChocolateyEnvironmentDebug", "true"); Environment.SetEnvironmentVariable("ChocolateyEnvironmentVerbose", "true"); var result = new PowerShellExecutionResults(); - string commandToRun = wrap_script_with_module(chocoPowerShellScript, config); + + string commandToRun = wrap_script_with_module(chocoPowerShellScript, hookPreScriptPathList, hookPostScriptPathList, config); var host = new PoshHost(config); this.Log().Debug(() => "Calling built-in PowerShell host with ['{0}']".format_with(commandToRun.escape_curly_braces())); @@ -671,11 +738,11 @@ public PowerShellExecutionResults run_host(ChocolateyConfiguration config, strin if (host.ExitCode == 0) host.SetShouldExit(1); host.HostException = pipeline.PipelineStateInfo.Reason; break; + case PipelineState.Completed: if (host.ExitCode == -1) host.SetShouldExit(0); break; } - } } } diff --git a/src/chocolatey/infrastructure.app/services/PythonService.cs b/src/chocolatey/infrastructure.app/services/PythonService.cs index 972354c10c..4e59ed8b50 100644 --- a/src/chocolatey/infrastructure.app/services/PythonService.cs +++ b/src/chocolatey/infrastructure.app/services/PythonService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -97,13 +97,13 @@ private void set_install_dictionary(IDictionary args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "install", Required = true }); args.Add("_package_name_", new ExternalCommandArgument - { - ArgumentOption = "", - ArgumentValue = PACKAGE_NAME_TOKEN, - QuoteValue = false, - UseValueOnly = true, - Required = true - }); + { + ArgumentOption = "", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); } /// @@ -116,13 +116,13 @@ private void set_upgrade_dictionary(IDictionary args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "install", Required = true }); args.Add("_upgrade_", new ExternalCommandArgument { ArgumentOption = "--upgrade", Required = true }); args.Add("_package_name_", new ExternalCommandArgument - { - ArgumentOption = "", - ArgumentValue = PACKAGE_NAME_TOKEN, - QuoteValue = false, - UseValueOnly = true, - Required = true - }); + { + ArgumentOption = "", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); } /// @@ -135,13 +135,13 @@ private void set_uninstall_dictionary(IDictionary args) @@ -163,13 +163,11 @@ private void set_common_args(IDictionary args) UseValueOnly = true, Required = true }); - - } - public SourceType SourceType + public string SourceType { - get { return SourceType.python; } + get { return SourceTypes.PYTHON; } } public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs index 67a62df642..8cfed8d964 100644 --- a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +45,6 @@ public sealed class RubyGemsService : ISourceRunner public static readonly Regex PackageNameInstalledRegex = new Regex(@"Successfully installed (?<{0}>.*)\-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); public static readonly Regex PackageNameErrorRegex = new Regex(@"'(?<{0}>[^']*)'".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); - private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); @@ -72,7 +71,7 @@ private void set_list_dictionary(IDictionary ar { args.Add("_cmd_c_", new ExternalCommandArgument { ArgumentOption = "/c", Required = true }); args.Add("_gem_", new ExternalCommandArgument { ArgumentOption = "gem", Required = true }); - args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "list", Required = true}); + args.Add("_action_", new ExternalCommandArgument { ArgumentOption = "list", Required = true }); } /// @@ -80,36 +79,36 @@ private void set_list_dictionary(IDictionary ar /// private void set_install_dictionary(IDictionary args) { - args.Add("_cmd_c_", new ExternalCommandArgument {ArgumentOption = "/c", Required = true}); - args.Add("_gem_", new ExternalCommandArgument {ArgumentOption = "gem", Required = true}); - args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "install", Required = true}); + args.Add("_cmd_c_", new ExternalCommandArgument { ArgumentOption = "/c", Required = true }); + args.Add("_gem_", new ExternalCommandArgument { ArgumentOption = "gem", Required = true }); + args.Add("_action_", new ExternalCommandArgument { ArgumentOption = "install", Required = true }); args.Add("_package_name_", new ExternalCommandArgument - { - ArgumentOption = "package name ", - ArgumentValue = PACKAGE_NAME_TOKEN, - QuoteValue = false, - UseValueOnly = true, - Required = true - }); + { + ArgumentOption = "package name ", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); args.Add("Force", new ExternalCommandArgument - { - ArgumentOption = "-f ", - QuoteValue = false, - Required = false - }); + { + ArgumentOption = "-f ", + QuoteValue = false, + Required = false + }); args.Add("Version", new ExternalCommandArgument - { - ArgumentOption = "--version ", - QuoteValue = false, - Required = false - }); + { + ArgumentOption = "--version ", + QuoteValue = false, + Required = false + }); } - public SourceType SourceType + public string SourceType { - get { return SourceType.ruby; } + get { return SourceTypes.RUBY; } } public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) @@ -117,19 +116,19 @@ public void ensure_source_app_installed(ChocolateyConfiguration config, Action

_templateBinaryExtensions = new List { ".exe", ".msi", ".msu", ".msp", ".mst", @@ -45,10 +47,12 @@ public class TemplateService : ITemplateService private readonly string _builtInTemplateOverrideName = "default"; private readonly string _builtInTemplateName = "built-in"; + private readonly string _templateParameterCacheFilename = ".parameters"; - public TemplateService(IFileSystem fileSystem) + public TemplateService(IFileSystem fileSystem, IXmlService xmlService) { _fileSystem = fileSystem; + _xmlService = xmlService; } public void generate_noop(ChocolateyConfiguration configuration) @@ -148,6 +152,7 @@ public void generate(ChocolateyConfiguration configuration) configuration.NewCommand.TemplateName = string.IsNullOrWhiteSpace(configuration.NewCommand.TemplateName) ? "default" : configuration.NewCommand.TemplateName; var templatePath = _fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.NewCommand.TemplateName); + var templateParameterCachePath = _fileSystem.combine_paths(templatePath, _templateParameterCacheFilename); if (!_fileSystem.directory_exists(templatePath)) throw new ApplicationException("Unable to find path to requested template '{0}'. Path should be '{1}'".format_with(configuration.NewCommand.TemplateName, templatePath)); this.Log().Info(configuration.QuietOutput ? logger : ChocolateyLoggers.Important, "Generating package from custom template at '{0}'.".format_with(templatePath)); @@ -174,6 +179,10 @@ public void generate(ChocolateyConfiguration configuration) this.Log().Debug(" Treating template file ('{0}') as binary instead of replacing templated values.".format_with(_fileSystem.get_file_name(file))); _fileSystem.copy_file(file, packageFileLocation, overwriteExisting:true); } + else if (templateParameterCachePath.is_equal_to(file)) + { + this.Log().Debug("{0} is the parameter cache file, ignoring".format_with(file)); + } else { generate_file_from_template(configuration, tokens, _fileSystem.read_file(file), packageFileLocation, Encoding.UTF8); @@ -280,6 +289,7 @@ protected void list_custom_template_info(ChocolateyConfiguration configuration, .combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name), "*", SearchOption.AllDirectories))); var isOverridingBuiltIn = configuration.TemplateCommand.Name == _builtInTemplateOverrideName; var isDefault = string.IsNullOrWhiteSpace(configuration.DefaultTemplateName) ? isOverridingBuiltIn : (configuration.DefaultTemplateName == configuration.TemplateCommand.Name); + var templateParams = " {0}".format_with(string.Join("{0} ".format_with(Environment.NewLine), get_template_parameters(configuration, templateInstalledViaPackage))); if (configuration.RegularOutput) { @@ -292,6 +302,8 @@ protected void list_custom_template_info(ChocolateyConfiguration configuration, {5}{6} List of files: {7} +List of Parameters: +{8} ".format_with(configuration.TemplateCommand.Name, pkgVersion, isDefault, @@ -299,7 +311,8 @@ protected void list_custom_template_info(ChocolateyConfiguration configuration, pkgTitle, string.IsNullOrEmpty(pkgSummary) ? "Template not installed as a package" : "Summary: {0}".format_with(pkgSummary), string.IsNullOrEmpty(pkgDescription) ? string.Empty : "{0}Description:{0} {1}".format_with(Environment.NewLine, pkgDescription), - pkgFiles)); + pkgFiles, + templateParams)); } else { @@ -345,5 +358,51 @@ protected void list_built_in_template_info(ChocolateyConfiguration configuration } } } + + protected IEnumerable get_template_parameters(ChocolateyConfiguration configuration, bool templateInstalledViaPackage) + { + // If the template was installed via package, the cache file gets removed on upgrade, so the cache file would be up to date if it exists + if (templateInstalledViaPackage) + { + var templateDirectory = _fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name); + var cacheFilePath = _fileSystem.combine_paths(templateDirectory, _templateParameterCacheFilename); + + if (!_fileSystem.file_exists(cacheFilePath)) + { + _xmlService.serialize(get_template_parameters_from_files(configuration).ToList(), cacheFilePath); + } + + return _xmlService.deserialize>(cacheFilePath); + } + // If the template is not installed via a package, always read the parameters directly as the template may have been updated manually + + return get_template_parameters_from_files(configuration).ToList(); + } + + protected HashSet get_template_parameters_from_files(ChocolateyConfiguration configuration) + { + var filesList = _fileSystem.get_files(_fileSystem.combine_paths(ApplicationParameters.TemplatesLocation, configuration.TemplateCommand.Name), "*", SearchOption.AllDirectories); + var parametersList = new HashSet(); + + foreach (var filePath in filesList) + { + if (_templateBinaryExtensions.Contains(_fileSystem.get_file_extension(filePath))) + { + this.Log().Debug("{0} is a binary file, not reading parameters".format_with(filePath)); + continue; + } + + if (_fileSystem.get_file_name(filePath) == _templateParameterCacheFilename) + { + this.Log().Debug("{0} is the parameter cache file, not reading parameters".format_with(filePath)); + continue; + } + + var fileContents = _fileSystem.read_file(filePath); + parametersList.UnionWith(TokenReplacer.get_tokens(fileContents, "[[", "]]")); + } + + return parametersList; + } } } diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 6c36381614..4d5cfe6d14 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +40,7 @@ public sealed class WebPiService : ISourceRunner public static readonly Regex InstallingRegex = new Regex(@"Started installing:", RegexOptions.Compiled); public static readonly Regex InstalledRegex = new Regex(@"Install completed \(Success\):", RegexOptions.Compiled); public static readonly Regex AlreadyInstalledRegex = new Regex(@"No products to be installed \(either not available or already installed\)", RegexOptions.Compiled); + //public static readonly Regex NotInstalled = new Regex(@"not installed", RegexOptions.Compiled); public static readonly Regex PackageNameRegex = new Regex(@"'(?<{0}>[^']*)'".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); @@ -67,8 +68,8 @@ private void set_cmd_args_dictionaries() ///

private void set_list_dictionary(IDictionary args) { - args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/List", Required = true}); - args.Add("_list_option_", new ExternalCommandArgument {ArgumentOption = "/ListOption:All", Required = true}); + args.Add("_action_", new ExternalCommandArgument { ArgumentOption = "/List", Required = true }); + args.Add("_list_option_", new ExternalCommandArgument { ArgumentOption = "/ListOption:All", Required = true }); } /// @@ -76,21 +77,21 @@ private void set_list_dictionary(IDictionary ar /// private void set_install_dictionary(IDictionary args) { - args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/Install", Required = true}); - args.Add("_accept_eula_", new ExternalCommandArgument {ArgumentOption = "/AcceptEula", Required = true}); - args.Add("_suppress_reboot_", new ExternalCommandArgument {ArgumentOption = "/SuppressReboot", Required = true}); + args.Add("_action_", new ExternalCommandArgument { ArgumentOption = "/Install", Required = true }); + args.Add("_accept_eula_", new ExternalCommandArgument { ArgumentOption = "/AcceptEula", Required = true }); + args.Add("_suppress_reboot_", new ExternalCommandArgument { ArgumentOption = "/SuppressReboot", Required = true }); args.Add("_package_name_", new ExternalCommandArgument - { - ArgumentOption = "/Products:", - ArgumentValue = PACKAGE_NAME_TOKEN, - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "/Products:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); } - public SourceType SourceType + public string SourceType { - get { return SourceType.webpi; } + get { return SourceTypes.WEBPI; } } public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) @@ -98,19 +99,19 @@ public void ensure_source_app_installed(ChocolateyConfiguration config, Action

install_run(ChocolateyConfigu this.Log().Error(() => "[{0}] {1}".format_with(APP_NAME, e.Data.escape_curly_braces())); }, updateProcessPath: false, - allowUseWindow:true + allowUseWindow: true ); if (exitCode != 0) diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs index 1011ff8824..a960c1e9ca 100644 --- a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -92,8 +92,8 @@ private void set_cmd_args_dictionaries() private void set_list_dictionary(IDictionary args) { set_common_args(args); - args.Add("_features_", new ExternalCommandArgument {ArgumentOption = FEATURES_VALUE, Required = true}); - args.Add("_format_", new ExternalCommandArgument {ArgumentOption = FORMAT_VALUE, Required = true}); + args.Add("_features_", new ExternalCommandArgument { ArgumentOption = FEATURES_VALUE, Required = true }); + args.Add("_format_", new ExternalCommandArgument { ArgumentOption = FORMAT_VALUE, Required = true }); } ///

@@ -103,16 +103,16 @@ private void set_install_dictionary(IDictionary { set_common_args(args); - args.Add("_feature_", new ExternalCommandArgument {ArgumentOption = "/Enable-Feature", Required = true}); + args.Add("_feature_", new ExternalCommandArgument { ArgumentOption = "/Enable-Feature", Required = true }); args.Add("_package_name_", new ExternalCommandArgument - { - ArgumentOption = "/FeatureName:", - ArgumentValue = PACKAGE_NAME_TOKEN, - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "/FeatureName:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); // /All should be the final argument. - args.Add("_all_", new ExternalCommandArgument {ArgumentOption = ALL_TOKEN, Required = true}); + args.Add("_all_", new ExternalCommandArgument { ArgumentOption = ALL_TOKEN, Required = true }); } /// @@ -125,34 +125,34 @@ private void set_uninstall_dictionary(IDictionary args) { - args.Add("_online_", new ExternalCommandArgument {ArgumentOption = "/Online", Required = true}); - args.Add("_english_", new ExternalCommandArgument {ArgumentOption = "/English", Required = true}); + args.Add("_online_", new ExternalCommandArgument { ArgumentOption = "/Online", Required = true }); + args.Add("_english_", new ExternalCommandArgument { ArgumentOption = "/English", Required = true }); args.Add("_loglevel_", new ExternalCommandArgument - { - ArgumentOption = "/LogLevel=", - ArgumentValue = LOG_LEVEL_TOKEN, - QuoteValue = false, - Required = true - }); + { + ArgumentOption = "/LogLevel=", + ArgumentValue = LOG_LEVEL_TOKEN, + QuoteValue = false, + Required = true + }); - args.Add("_no_restart_", new ExternalCommandArgument {ArgumentOption = "/NoRestart", Required = true}); + args.Add("_no_restart_", new ExternalCommandArgument { ArgumentOption = "/NoRestart", Required = true }); } - public SourceType SourceType + public string SourceType { - get { return SourceType.windowsfeatures; } + get { return SourceTypes.WINDOWS_FEATURES; } } public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) diff --git a/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs b/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs index 8be7a5687b..5989bdffe5 100644 --- a/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs +++ b/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs @@ -26,7 +26,7 @@ public class ChocolateyInstallTemplate # 1. See the _TODO.md that is generated top level and read through that # 2. Follow the documentation below to learn how to create a package for the package type you are creating. # 3. In Chocolatey scripts, ALWAYS use absolute paths - $toolsDir gets you to the package's tools directory. -$ErrorActionPreference = 'Stop'; # stop on all errors[[AutomaticPackageNotesInstaller]] +$ErrorActionPreference = 'Stop' # stop on all errors[[AutomaticPackageNotesInstaller]] $toolsDir = ""$(Split-Path -parent $MyInvocation.MyCommand.Definition)"" # Internal packages (organizations) or software that has redistribution rights (community repo) # - Use `Install-ChocolateyInstallPackage` instead of `Install-ChocolateyPackage` diff --git a/src/chocolatey/infrastructure.app/templates/ChocolateyUninstallTemplate.cs b/src/chocolatey/infrastructure.app/templates/ChocolateyUninstallTemplate.cs index 086ccdb212..5057904ac8 100644 --- a/src/chocolatey/infrastructure.app/templates/ChocolateyUninstallTemplate.cs +++ b/src/chocolatey/infrastructure.app/templates/ChocolateyUninstallTemplate.cs @@ -31,7 +31,7 @@ public class ChocolateyUninstallTemplate ## If this is an MSI, ensure 'softwareName' is appropriate, then clean up comments and you are done. ## If this is an exe, change fileType, silentArgs, and validExitCodes -$ErrorActionPreference = 'Stop'; # stop on all errors +$ErrorActionPreference = 'Stop' # stop on all errors $packageArgs = @{ packageName = $env:ChocolateyPackageName softwareName = '[[PackageName]]*' #part or all of the Display Name as you see it in Programs and Features. It should be enough to be unique diff --git a/src/chocolatey/infrastructure/adapters/Assembly.cs b/src/chocolatey/infrastructure/adapters/Assembly.cs index 9d9e5e30c2..9a0472a7c9 100644 --- a/src/chocolatey/infrastructure/adapters/Assembly.cs +++ b/src/chocolatey/infrastructure/adapters/Assembly.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,11 +88,21 @@ public Type GetType(String name, bool throwOnError, bool ignoreCase) return _assembly.GetType(name,throwOnError, ignoreCase); } + public Type[] GetTypes() + { + return _assembly.GetTypes(); + } + public static IAssembly Load(byte[] rawAssembly) { return new Assembly(System.Reflection.Assembly.Load(rawAssembly)); } + public static IAssembly Load(byte[] rawAssembly, byte[] rawSymbols) + { + return new Assembly(System.Reflection.Assembly.Load(rawAssembly, rawSymbols)); + } + public static IAssembly LoadFile(string path) { return new Assembly(System.Reflection.Assembly.LoadFile(path)); diff --git a/src/chocolatey/infrastructure/adapters/IAssembly.cs b/src/chocolatey/infrastructure/adapters/IAssembly.cs index 916761deda..bb1caca0ff 100644 --- a/src/chocolatey/infrastructure/adapters/IAssembly.cs +++ b/src/chocolatey/infrastructure/adapters/IAssembly.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -86,6 +86,8 @@ public interface IAssembly Type GetType(String name, bool throwOnError, bool ignoreCase); + Type[] GetTypes(); + /// /// Loads the specified manifest resource from this assembly. /// diff --git a/src/chocolatey/infrastructure/extractors/AssemblyFileExtractor.cs b/src/chocolatey/infrastructure/extractors/AssemblyFileExtractor.cs index e1121b1cd4..a6d3111856 100644 --- a/src/chocolatey/infrastructure/extractors/AssemblyFileExtractor.cs +++ b/src/chocolatey/infrastructure/extractors/AssemblyFileExtractor.cs @@ -66,8 +66,8 @@ public static void extract_text_file_from_assembly(IFileSystem fileSystem, IAsse /// /// if set to true [overwrite existing]. /// - /// Throw an error if there are issues - public static void extract_binary_file_from_assembly(IFileSystem fileSystem, IAssembly assembly, string manifestLocation, string filePath, bool overwriteExisting = false, bool throwError = true) + /// Throw an error if there are issues + public static void extract_binary_file_from_assembly(IFileSystem fileSystem, IAssembly assembly, string manifestLocation, string filePath, bool overwriteExisting = false, bool throwEror = true) { if (overwriteExisting || !fileSystem.file_exists(filePath)) { @@ -78,10 +78,10 @@ public static void extract_binary_file_from_assembly(IFileSystem fileSystem, IAs fileSystem.write_file(filePath, () => assembly.get_manifest_stream(manifestLocation)); }, errorMessage:"Unable to extract binary", - throwError: throwError, + throwError: throwEror, logWarningInsteadOfError: false, - logDebugInsteadOfError: !throwError, - isSilent: !throwError); + logDebugInsteadOfError: !throwEror, + isSilent: !throwEror); } } diff --git a/src/chocolatey/infrastructure/information/ExtensionInformation.cs b/src/chocolatey/infrastructure/information/ExtensionInformation.cs new file mode 100644 index 0000000000..219f86cb08 --- /dev/null +++ b/src/chocolatey/infrastructure/information/ExtensionInformation.cs @@ -0,0 +1,72 @@ +// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.information +{ + using System.Collections.Generic; + using chocolatey.infrastructure.adapters; + + public class ExtensionInformation + { + public ExtensionInformation(IAssembly assembly) + { + Name = assembly.GetName().Name; + Version = VersionInformation.get_current_informational_version(assembly); + Status = ExtensionStatus.Unknown; + } + + public string Name { get; private set; } + + public string Version { get; private set; } + + public ExtensionStatus Status { get; internal set; } + + public override bool Equals(object obj) + { + ExtensionInformation information = obj as ExtensionInformation; + return !ReferenceEquals(information, null) && + Name == information.Name && + Version == information.Version; + } + + public override int GetHashCode() + { + // We do this in an uncheched statement so there won't be any arithmetic exceptions + unchecked + { + int hashCode = 14; + hashCode = (hashCode * 6) + + EqualityComparer.Default.GetHashCode(Name) + + EqualityComparer.Default.GetHashCode(Version); + return hashCode; + } + } + + public override string ToString() + { + return "{0} v{1}".format_with(Name, Version); + } + } + + public enum ExtensionStatus + { + Unknown = 0, + Loaded, + Enabled = Loaded, + Disabled, + Failed + } +} diff --git a/src/chocolatey/infrastructure/licensing/License.cs b/src/chocolatey/infrastructure/licensing/License.cs index ed07bed2fd..d6c87a8f6d 100644 --- a/src/chocolatey/infrastructure/licensing/License.cs +++ b/src/chocolatey/infrastructure/licensing/License.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,13 +31,12 @@ public static ChocolateyLicense validate_license() { try { - #if FORCE_CHOCOLATEY_OFFICIAL_KEY var chocolateyPublicKey = ApplicationParameters.OfficialChocolateyPublicKey; #else var chocolateyPublicKey = ApplicationParameters.UnofficialChocolateyPublicKey; #endif - var licensedAssembly = AssemblyResolution.resolve_or_load_assembly(ApplicationParameters.LicensedChocolateyAssemblySimpleName, chocolateyPublicKey, ApplicationParameters.LicensedAssemblyLocation); + var licensedAssembly = AssemblyResolution.load_extension(ApplicationParameters.LicensedChocolateyAssemblySimpleName); if (licensedAssembly == null) { diff --git a/src/chocolatey/infrastructure/registration/AssemblyResolution.cs b/src/chocolatey/infrastructure/registration/AssemblyResolution.cs index 25cf500e11..d9161d5ec2 100644 --- a/src/chocolatey/infrastructure/registration/AssemblyResolution.cs +++ b/src/chocolatey/infrastructure/registration/AssemblyResolution.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,9 +17,11 @@ namespace chocolatey.infrastructure.registration { using System; + using System.Collections.Concurrent; using System.Linq; using System.Threading; using adapters; + using chocolatey.infrastructure.app; using filesystem; public class AssemblyResolution @@ -27,16 +29,83 @@ public class AssemblyResolution private const int LOCK_RESOLUTION_TIMEOUT_SECONDS = 5; private static readonly object _lockObject = new object(); + private static readonly ConcurrentDictionary _loadedAssemblies = new ConcurrentDictionary(); + /// /// Resolves or loads an assembly. If an assembly is already loaded, no need to reload it. /// /// Simple Name of the assembly, such as "chocolatey" /// The public key token. /// The assembly file location. Typically the path to the DLL on disk. + /// Whether any existing library that has previously been loaded should be ignored or not. /// An assembly /// Unable to enter synchronized code to determine assembly loading - public static IAssembly resolve_or_load_assembly(string assemblySimpleName, string publicKeyToken, string assemblyFileLocation) + public static IAssembly resolve_or_load_assembly(string assemblySimpleName, string publicKeyToken, string assemblyFileLocation, bool ignoreExisting = false) + { + var lockTaken = false; + try + { + Monitor.TryEnter(_lockObject, TimeSpan.FromSeconds(LOCK_RESOLUTION_TIMEOUT_SECONDS), ref lockTaken); + } + catch (Exception) + { + throw new Exception("Unable to enter synchronized code to determine assembly loading"); + } + + IAssembly resolvedAssembly = null; + + if (lockTaken) + { + try + { + if (!ignoreExisting) + { + resolvedAssembly = resolve_assembly(assemblySimpleName, publicKeyToken); + } + + if (resolvedAssembly == null) + { + var tempAssembly = Assembly.Load(FileSystem.read_binary_file_into_byte_array(assemblyFileLocation)); + + if (tempAssembly == null) + { + return null; + } + + if (string.IsNullOrWhiteSpace(publicKeyToken) || tempAssembly.GetName().get_public_key_token().is_equal_to(publicKeyToken)) + { + "chocolatey".Log().Debug("Loading up '{0}' assembly type from '{1}'".format_with(assemblySimpleName, assemblyFileLocation)); + resolvedAssembly = tempAssembly; + _loadedAssemblies.TryAdd(assemblySimpleName.to_lower(), resolvedAssembly); + + if (assemblySimpleName.is_equal_to("choco")) + { + _loadedAssemblies.TryAdd("chocolatey", resolvedAssembly); + } + else if (assemblySimpleName.is_equal_to("chocolatey")) + { + _loadedAssemblies.TryAdd("choco", resolvedAssembly); + } + } + } + } + finally + { + Monitor.Pulse(_lockObject); + Monitor.Exit(_lockObject); + } + } + + return resolvedAssembly; + } + + public static IAssembly load_assembly(string assemblySimpleName, string assemblyFileLocation, params string[] publicKeyTokens) { + if (publicKeyTokens == null || publicKeyTokens.Length == 0) + { + publicKeyTokens = new[] { string.Empty }; + } + var lockTaken = false; try { @@ -48,25 +117,104 @@ public static IAssembly resolve_or_load_assembly(string assemblySimpleName, stri } IAssembly resolvedAssembly = null; + if (lockTaken) { try { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name.is_equal_to(assemblySimpleName)).or_empty_list_if_null()) + IAssembly tempAssembly; +#if FORCE_OFFICIAL_KEY + tempAssembly = Assembly.Load(FileSystem.read_binary_file_into_byte_array(assemblyFileLocation)); +#else + var symbolFile = System.IO.Path.ChangeExtension(assemblyFileLocation, ".pdb"); + if (System.IO.File.Exists(symbolFile)) { - if (string.IsNullOrWhiteSpace(publicKeyToken) || assembly.GetName().get_public_key_token().is_equal_to(publicKeyToken)) + tempAssembly = Assembly.Load( + FileSystem.read_binary_file_into_byte_array(assemblyFileLocation), + FileSystem.read_binary_file_into_byte_array(symbolFile)); + } + else + { + tempAssembly = Assembly.Load(FileSystem.read_binary_file_into_byte_array(assemblyFileLocation)); + } +#endif + + if (tempAssembly == null) + { + return null; + } + + foreach (var publicKeyToken in publicKeyTokens) + { + if (string.IsNullOrWhiteSpace(publicKeyToken) || tempAssembly.GetName().get_public_key_token().is_equal_to(publicKeyToken)) { - "AssemblyResolver".Log().Debug("Returning loaded assembly type for '{0}'".format_with(assemblySimpleName)); - resolvedAssembly = Assembly.set_assembly(assembly); + "chocolatey".Log().Debug("Loading up '{0}' assembly type from '{1}'".format_with(assemblySimpleName, assemblyFileLocation)); + resolvedAssembly = tempAssembly; + + _loadedAssemblies.TryAdd(assemblySimpleName.to_lower(), resolvedAssembly); + + if (assemblySimpleName.is_equal_to("choco")) + { + _loadedAssemblies.TryAdd("chocolatey", resolvedAssembly); + } + else if (assemblySimpleName.is_equal_to("chocolatey")) + { + _loadedAssemblies.TryAdd("choco", resolvedAssembly); + } + break; } } + } + finally + { + Monitor.Pulse(_lockObject); + Monitor.Exit(_lockObject); + } + } - if (resolvedAssembly == null) + return resolvedAssembly; + } + + /// + /// Resolves or loads an assembly. If an assembly is already loaded, no need to reload it. + /// + /// Simple Name of the assembly, such as "chocolatey" + /// The public key tokens the assembly may be signed with. + /// + /// An assembly + /// Unable to enter synchronized code to determine assembly loading + public static IAssembly resolve_existing_assembly(string assemblySimpleName, params string[] publicKeyTokens) + { + if (publicKeyTokens == null || publicKeyTokens.Length == 0) + { + publicKeyTokens = new[] { string.Empty }; + } + + var lockTaken = false; + try + { + Monitor.TryEnter(_lockObject, TimeSpan.FromSeconds(LOCK_RESOLUTION_TIMEOUT_SECONDS), ref lockTaken); + } + catch (Exception) + { + throw new Exception("Unable to enter synchronized code to determine assembly loading"); + } + + IAssembly resolvedAssembly = null; + + if (lockTaken) + { + try + { + foreach (var publicKeyToken in publicKeyTokens) { - "AssemblyResolver".Log().Debug("Loading up '{0}' assembly type from '{1}'".format_with(assemblySimpleName, assemblyFileLocation)); - // avoid locking by reading in the bytes of the file and then passing that to Assembly.Load - resolvedAssembly = Assembly.Load(FileSystem.read_binary_file_into_byte_array(assemblyFileLocation)); + resolvedAssembly = resolve_assembly(assemblySimpleName, publicKeyToken); + + if (resolvedAssembly != null) + { + break; + } } } finally @@ -78,5 +226,57 @@ public static IAssembly resolve_or_load_assembly(string assemblySimpleName, stri return resolvedAssembly; } + + private static IAssembly resolve_assembly(string assemblySimpleName, string publicKeyToken) + { + IAssembly resolvedAssembly = null; + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name.is_equal_to(assemblySimpleName)).or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(publicKeyToken) || assembly.GetName().get_public_key_token().is_equal_to(publicKeyToken)) + { + "chocolatey".Log().Debug("Returning loaded assembly type for '{0}'".format_with(assemblySimpleName)); + resolvedAssembly = Assembly.set_assembly(assembly); + break; + } + } + + IAssembly tempAssembly; + + if (_loadedAssemblies.TryGetValue(assemblySimpleName.to_lower(), out tempAssembly)) + { + if (string.IsNullOrWhiteSpace(publicKeyToken) || tempAssembly.GetName().get_public_key_token().is_equal_to(publicKeyToken)) + { + "chocolatey".Log().Debug("Returning loaded assembly type for '{0}'".format_with(assemblySimpleName)); + resolvedAssembly = tempAssembly; + } + } + + return resolvedAssembly; + } + + public static IAssembly load_extension(string assemblySimpleName) + { +#if FORCE_CHOCOLATEY_OFFICIAL_KEY + var chocolateyPublicKey = ApplicationParameters.OfficialChocolateyPublicKey; +#else + var chocolateyPublicKey = ApplicationParameters.UnofficialChocolateyPublicKey; +#endif + + var fullName = "{0}, Version=0.0.0.0, Culture=neutral, PublicKeyToken={1}".format_with( + assemblySimpleName, + chocolateyPublicKey); + + // We use Reflection Assembly Load directly to allow .NET assembly resolving + // to handle everything. + var assembly = System.Reflection.Assembly.Load(fullName); + + if (assembly != null) + { + return Assembly.set_assembly(assembly); + } + + return null; + } } } diff --git a/src/chocolatey/infrastructure/registration/SimpleInjectorContainer.cs b/src/chocolatey/infrastructure/registration/SimpleInjectorContainer.cs index e9c956ae7e..e1b5b0e8ef 100644 --- a/src/chocolatey/infrastructure/registration/SimpleInjectorContainer.cs +++ b/src/chocolatey/infrastructure/registration/SimpleInjectorContainer.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,13 @@ namespace chocolatey.infrastructure.registration { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; using System.Reflection; using app; using app.registration; + using chocolatey.infrastructure.information; + using chocolatey.infrastructure.licensing; using logging; using SimpleInjector; @@ -39,12 +43,12 @@ public static class SimpleInjectorContainer private static bool _verifyContainer = false; #endif - public static bool VerifyContainer { + public static bool VerifyContainer + { get { return _verifyContainer; } set { _verifyContainer = value; } } - /// /// Add a component registry class to the container. /// Must have `public void RegisterComponents(Container container)` @@ -72,11 +76,13 @@ private static Container initialize() container.Options.ConstructorResolutionBehavior = new SimpleInjectorContainerResolutionBehavior(originalConstructorResolutionBehavior); var binding = new ContainerBinding(); - binding.RegisterComponents(container); + var extensions = binding.RegisterComponents(container); + + // TODO: Remove once we can do a breaking release, ie 2.0.0 foreach (var componentRegistry in _componentRegistries) { - load_component_registry(componentRegistry, container); + load_component_registry(componentRegistry, container, extensions); } if (_verifyContainer) container.Verify(); @@ -89,26 +95,34 @@ private static Container initialize() /// /// The component registry. /// The container. - private static void load_component_registry(Type componentRegistry, Container container) + private static void load_component_registry(Type componentRegistry, Container container, IEnumerable extensions) { if (componentRegistry == null) { - "chocolatey".Log().Warn(ChocolateyLoggers.Important, -@"Unable to register licensed components. This is likely related to a + if (!extensions.Any(e => e.Name.is_equal_to("chocolatey.licensed"))) + { + "chocolatey".Log().Warn(ChocolateyLoggers.Important, + @"Unable to register licensed components. This is likely related to a missing or outdated licensed DLL."); + } return; } try { - object componentClass = Activator.CreateInstance(componentRegistry); - - componentRegistry.InvokeMember( - REGISTER_COMPONENTS_METHOD, - BindingFlags.InvokeMethod, - null, - componentClass, - new Object[] { container } - ); + if (!extensions.Any(e => e.Name.is_equal_to(componentRegistry.Assembly.GetName().Name))) + { + var registrations = container.GetCurrentRegistrations(); + + object componentClass = Activator.CreateInstance(componentRegistry); + + componentRegistry.InvokeMember( + REGISTER_COMPONENTS_METHOD, + BindingFlags.InvokeMethod, + null, + componentClass, + new Object[] { container } + ); + } } catch (Exception ex) { diff --git a/src/chocolatey/infrastructure/tasks/ITask.cs b/src/chocolatey/infrastructure/tasks/ITask.cs index 440a93243b..c331cb9ed9 100644 --- a/src/chocolatey/infrastructure/tasks/ITask.cs +++ b/src/chocolatey/infrastructure/tasks/ITask.cs @@ -16,9 +16,12 @@ namespace chocolatey.infrastructure.tasks { + using chocolatey.infrastructure.app.attributes; + /// /// Interface for all runners. /// + [MultiService] public interface ITask { /// diff --git a/src/chocolatey/infrastructure/tokens/TokenReplacer.cs b/src/chocolatey/infrastructure/tokens/TokenReplacer.cs index 50b7541abb..8c1e4028d0 100644 --- a/src/chocolatey/infrastructure/tokens/TokenReplacer.cs +++ b/src/chocolatey/infrastructure/tokens/TokenReplacer.cs @@ -61,5 +61,16 @@ private static IDictionary create_dictionary_from_configuration< return propertyDictionary; } + + public static IEnumerable get_tokens(string textWithTokens, string tokenPrefix = "[[", string tokenSuffix = "]]") + { + var regexMatches = Regex.Matches(textWithTokens, "{0}(?\\w+){1}" + .format_with(Regex.Escape(tokenPrefix), Regex.Escape(tokenSuffix)) + ); + foreach (Match regexMatch in regexMatches) + { + yield return regexMatch.Groups["key"].to_string(); + } + } } } diff --git a/src/chocolatey/infrastructure/validations/IValidation.cs b/src/chocolatey/infrastructure/validations/IValidation.cs index 19a0cf9310..c0aab571e8 100644 --- a/src/chocolatey/infrastructure/validations/IValidation.cs +++ b/src/chocolatey/infrastructure/validations/IValidation.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2021 Chocolatey Software, Inc +// Copyright © 2017 - 2022 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,12 @@ namespace chocolatey.infrastructure.validations { using System.Collections.Generic; using app.configuration; + using chocolatey.infrastructure.app.attributes; /// /// Interface for all validations /// + [MultiService] public interface IValidation { /// diff --git a/src/chocolatey/packages.config b/src/chocolatey/packages.config index 2fcf30ea40..b3af30a6eb 100644 --- a/src/chocolatey/packages.config +++ b/src/chocolatey/packages.config @@ -1,5 +1,4 @@  - @@ -7,5 +6,5 @@ - + \ No newline at end of file diff --git a/tests/Vagrantfile b/tests/Vagrantfile index 2b2e4b2149..fec687b61a 100644 --- a/tests/Vagrantfile +++ b/tests/Vagrantfile @@ -60,6 +60,7 @@ Vagrant.configure("2") do |config| Write-Host "Build complete. Executing tests." # $env:TEST_KITCHEN = 1 + $env:VM_RUNNING = 1 ./Invoke-Tests.ps1 SHELL end diff --git a/tests/chocolatey-tests/BundledApplications.Tests.ps1 b/tests/chocolatey-tests/BundledApplications.Tests.ps1 index 2f90f422f7..bb41c55a55 100644 --- a/tests/chocolatey-tests/BundledApplications.Tests.ps1 +++ b/tests/chocolatey-tests/BundledApplications.Tests.ps1 @@ -6,6 +6,7 @@ Describe 'Ensuring correct version of is installed' -Tag BundledApplicati ) -Skip:(-not (Test-ChocolateyVersionEqualOrHigherThan "1.0.0")) { Context ' is correctly installed' -Skip:(-not (Test-ChocolateyVersionEqualOrHigherThan $ChocolateyVersion)) { BeforeAll { + # Because we're not modifying the install in any way, there is no need to Initialize-ChocolateyTestInstall $ToolPath = "$env:ChocolateyInstall/tools/$Name.exe" # TODO: Encapsulate in an environment variable once kitchen-pester has new version - https://github.com/chocolatey/choco/issues/2692 $Thumbprint = '83AC7D88C66CB8680BCE802E0F0F5C179722764B' diff --git a/tests/chocolatey-tests/choco-template.Tests.ps1 b/tests/chocolatey-tests/choco-template.Tests.ps1 deleted file mode 100644 index 19900d8fb2..0000000000 --- a/tests/chocolatey-tests/choco-template.Tests.ps1 +++ /dev/null @@ -1,167 +0,0 @@ -Import-Module helpers/common-helpers - -Describe "choco <_>" -ForEach @( - "template" - "templates" -) -Tag Chocolatey, TemplateCommand { - BeforeDiscovery { - - } - - BeforeAll { - Initialize-ChocolateyTestInstall - - New-ChocolateyInstallSnapshot - } - - AfterAll { - Remove-ChocolateyTestInstall - } - - Context "Running without subcommand specified" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $Output = Invoke-Choco $_ - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 - } - - It "Displays the templates expected" { - $Output.Lines | Should -Contain 'msi 1.0.2' - $Output.Lines | Should -Contain 'zip 1.0.0' - } - } - - Context "Running with list subcommand" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $Output = Invoke-Choco $_ list - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 - } - - It "Displays the templates expected" { - $Output.Lines | Should -Contain 'msi 1.0.2' - $Output.Lines | Should -Contain 'zip 1.0.0' - } - } - - Context "Running with info subcommand specified with no additional parameters" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $Output = Invoke-Choco $_ info - } - - It "Exits with Failure (1)" { - $Output.ExitCode | Should -Be 1 - } - - It "Displays error with correct format" { - $Output.Lines | Should -Contain "When specifying the subcommand 'info', you must also specify --name." - } - } - - Context "Running with info subcommand specified with --name parameters" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $Output = Invoke-Choco $_ info --name msi - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 - } - - It "Displays template information" { - $Output.Lines | Should -Contain "Template name: msi" - $Output.Lines | Should -Contain "Version: 1.0.2" - $Output.Lines | Should -Contain "Default template: False" - $Output.Lines | Should -Contain "Summary: MSI Chocolatey template" - $Output.Lines | Should -Contain "### Chocolatey MSI template" - $Output.Lines | Should -Contain "This adds a template for MSI packages." - $Output.Lines | Should -Contain "List of files:" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\msi.nuspec" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\ReadMe.md" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\tools\chocolateybeforemodify.ps1" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\tools\chocolateyinstall.ps1" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\tools\chocolateyuninstall.ps1" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\tools\LICENSE.txt" - $Output.Lines | Should -Contain "$env:ChocolateyInstall\templates\msi\tools\VERIFICATION.txt" - } - } - - Context "Running with no subcommand specified with --name parameter" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $Output = Invoke-Choco $_ --name msi - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 - } - - It "Displays template name and version" { - $Output.Lines | Should -Contain "msi 1.0.2" - } - } - - Context "Running without subcommand specified after default template name is specified" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $null = Invoke-Choco config set defaultTemplateName zip - - $Output = Invoke-Choco $_ - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 - } - - It "Displays the templates marking the default as expected" { - $Output.Lines | Should -Contain '* zip 1.0.0' - $Output.Lines | Should -Contain 'Built-in template is not default, it can be specified if the --built-in parameter is used' - } - } - - Context "Running without subcommand specified after an invalid default template name is specified" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install msi.template zip.template - - $null = Invoke-Choco config set defaultTemplateName zp - - $Output = Invoke-Choco $_ - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 - } - - It "Displays the templates marking the default as expected" { - $Output.Lines | Should -Contain 'zip 1.0.0' - $Output.Lines | Should -Contain 'Built-in template is default.' - } - } -} diff --git a/tests/chocolatey-tests/choco-upgrade.Tests.ps1 b/tests/chocolatey-tests/choco-upgrade.Tests.ps1 deleted file mode 100644 index 96809a3e09..0000000000 --- a/tests/chocolatey-tests/choco-upgrade.Tests.ps1 +++ /dev/null @@ -1,155 +0,0 @@ -Import-Module helpers/common-helpers - -Describe "choco upgrade" -Tag Chocolatey, UpgradeCommand { - BeforeAll { - Initialize-ChocolateyTestInstall - - New-ChocolateyInstallSnapshot - } - - AfterAll { - Remove-ChocolateyTestInstall - } - - Context "Can upgrade packages with dependencies containing side by side installations and outdated dependency" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install chocolatey-core.extension --version 1.3.0 --confirm - $null = Invoke-Choco install 7zip --version 16.04 --confirm - $null = Invoke-Choco install chocolatey-core.extension --version 1.3.5.1 --sxs --confirm - - $Output = Invoke-Choco upgrade 7zip --version 21.7 --confirm - } - - AfterAll { - $null = Invoke-Choco uninstall 7zip 7zip.install --confirm - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 -Because $Output.String - } - - It "Upgrades version of the package " -ForEach @( - @{ Name = "7zip"; OldVersion = "16.04" } - @{ Name = "7zip.install"; OldVersion = "16.04" } - @{ Name = "chocolatey-core.extension"; OldVersion = "1.3.0" } - ) { - "$env:ChocolateyInstall\lib\$Name\$Name.nuspec" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\$Name\$Name.nuspec" - $XML.package.metadata.version | Should -Not -Be $OldVersion - } - - It "Have not upgraded side by side installation of v" -ForEach @( - @{ Name = "chocolatey-core.extension"; Version = "1.3.5.1" } - ) { - "$env:ChocolateyInstall\lib\$Name.$Version\$Name.$Version.nupkg" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\$Name.$Version\$Name.$Version.nuspec" - $XML.package.metadata.id | Should -Be $Name - $XML.package.metadata.version | Should -Be $Version - } - - It "Outputs a message showing that upgrading was successful" { - $Output.String | SHould -Match "Chocolatey upgraded 3/3 packages\." - } - } - - Context "Can upgrade packages with dependencies containing side by side installations and up to date dependency" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install chocolatey-core.extension --version 1.3.3 --confirm - $null = Invoke-Choco install 7zip --version 16.04 --confirm - $null = Invoke-Choco install chocolatey-core.extension --version 1.3.5.1 --sxs --confirm - - $Output = Invoke-Choco upgrade 7zip --version 21.7 --confirm - } - - AfterAll { - $null = Invoke-Choco uninstall 7zip 7zip.install --confirm - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 -Because $Output.String - } - - It "Upgrades version of the package " -ForEach @( - @{ Name = "7zip"; OldVersion = "16.04" } - @{ Name = "7zip.install"; OldVersion = "16.04" } - ) { - "$env:ChocolateyInstall\lib\$Name\$Name.nuspec" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\$Name\$Name.nuspec" - $XML.package.metadata.version | Should -Not -Be $OldVersion - } - - It "Have not upgraded dependency " { - "$env:ChocolateyInstall\lib\chocolatey-core.extension\chocolatey-core.extension.nupkg" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\chocolatey-core.extension\chocolatey-core.extension.nuspec" - $XML.package.metadata.id | Should -Be 'chocolatey-core.extension' - $XML.package.metadata.version | Should -Be '1.3.3' - } - - It "Have not upgraded side by side installation of v" -ForEach @( - @{ Name = "chocolatey-core.extension"; Version = "1.3.5.1" } - ) { - "$env:ChocolateyInstall\lib\$Name.$Version\$Name.$Version.nupkg" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\$Name.$Version\$Name.$Version.nuspec" - $XML.package.metadata.id | Should -Be $Name - $XML.package.metadata.version | Should -Be $Version - } - - It "Outputs a message showing that upgrading was successful" { - $Output.String | SHould -Match "Chocolatey upgraded 2/2 packages\." - } - } - - - Context "Can upgrade packages with dependencies containing outdated side by side installations and up to date dependency" { - BeforeAll { - Restore-ChocolateyInstallSnapshot - - $null = Invoke-Choco install chocolatey-core.extension --version 1.3.3 --confirm - $null = Invoke-Choco install 7zip --version 16.04 --confirm - $null = Invoke-Choco install chocolatey-core.extension --version 1.3.0 --sxs --confirm - - $Output = Invoke-Choco upgrade 7zip --version 21.7 --confirm - } - - AfterAll { - $null = Invoke-Choco uninstall 7zip 7zip.install --confirm - } - - It "Exits with Success (0)" { - $Output.ExitCode | Should -Be 0 -Because $Output.String - } - - It "Upgrades version of the package " -ForEach @( - @{ Name = "7zip"; OldVersion = "16.04" } - @{ Name = "7zip.install"; OldVersion = "16.04" } - ) { - "$env:ChocolateyInstall\lib\$Name\$Name.nuspec" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\$Name\$Name.nuspec" - $XML.package.metadata.version | Should -Not -Be $OldVersion - } - - It "Have not upgraded dependency chocolatey-core.extension" { - "$env:ChocolateyInstall\lib\chocolatey-core.extension\chocolatey-core.extension.nupkg" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\chocolatey-core.extension\chocolatey-core.extension.nuspec" - $XML.package.metadata.id | Should -Be 'chocolatey-core.extension' - $XML.package.metadata.version | Should -Be '1.3.3' - } - - It "Have not upgraded side by side installation of v" -ForEach @( - @{ Name = "chocolatey-core.extension"; Version = "1.3.0" } - ) { - "$env:ChocolateyInstall\lib\$Name.$Version\$Name.$Version.nupkg" | Should -Exist - [xml]$XML = Get-Content "$env:ChocolateyInstall\lib\$Name.$Version\$Name.$Version.nuspec" - $XML.package.metadata.id | Should -Be $Name - $XML.package.metadata.version | Should -Be $Version - } - - It "Outputs a message showing that upgrading was successful" { - $Output.String | SHould -Match "Chocolatey upgraded 2/2 packages\." - } - } -} diff --git a/tests/chocolatey-tests/chocolatey.Tests.ps1 b/tests/chocolatey-tests/chocolatey.Tests.ps1 index f1e1b719a6..de3a47c88a 100644 --- a/tests/chocolatey-tests/chocolatey.Tests.ps1 +++ b/tests/chocolatey-tests/chocolatey.Tests.ps1 @@ -40,7 +40,7 @@ Describe "Ensuring Chocolatey is correctly installed" -Tag Environment, Chocolat } AfterAll { - Remove-ChocolateyInstallSnapshot + Remove-ChocolateyTestInstall } Context 'Chocolatey' { @@ -139,13 +139,7 @@ Describe "Ensuring Chocolatey is correctly installed" -Tag Environment, Chocolat # This is FossOnly for now as there are some undetermined errors here that do not seem to present inside of Chocolatey. https://gitlab.com/chocolatey/build-automation/chocolatey-test-kitchen/-/issues/39 It "Should be able to run the script in AllSigned mode" -Skip:($_ -notin $PowerShellFiles) -Tag FossOnly { - # The chocolateyScriptRunner expects some passed in values and results in two errors if they're not there. This accounts for that. - $expectedErrors = if ($FileUnderTest.Name -eq 'chocolateyScriptRunner.ps1') { - 2 - } - else { - 0 - } + $expectedErrors = 0 $command = "Import-Module $FileUnderTest -ErrorAction SilentlyContinue; exit `$error.count" & powershell.exe -noprofile -ExecutionPolicy AllSigned -command $command 2>$null $LastExitCode | Should -BeExactly $expectedErrors diff --git a/tests/chocolatey-tests/chocolateyProfile.Tests.ps1 b/tests/chocolatey-tests/chocolateyProfile.Tests.ps1 index 87f6b9cac2..862188693e 100644 --- a/tests/chocolatey-tests/chocolateyProfile.Tests.ps1 +++ b/tests/chocolatey-tests/chocolateyProfile.Tests.ps1 @@ -2,12 +2,14 @@ Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" Describe "Chocolatey Profile" -Tag Chocolatey, Profile, Environment { + # Because we're not modifying the install in any way, there is no need to Initialize-ChocolateyTestInstall BeforeDiscovery { $ExportNotPresent = $true if (Test-ChocolateyVersionEqualOrHigherThan -Version "0.10.16-beta") { $ExportNotPresent = $false } } + Context "Tab Completion" { It "Should Exist" { Test-Path Function:\TabExpansion | Should -BeTrue diff --git a/tests/chocolatey-tests/choco-apikey.Tests.ps1 b/tests/chocolatey-tests/commands/choco-apikey.Tests.ps1 similarity index 100% rename from tests/chocolatey-tests/choco-apikey.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-apikey.Tests.ps1 diff --git a/tests/chocolatey-tests/choco-config.Tests.ps1 b/tests/chocolatey-tests/commands/choco-config.Tests.ps1 similarity index 100% rename from tests/chocolatey-tests/choco-config.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-config.Tests.ps1 diff --git a/tests/chocolatey-tests/choco-deprecated.Tests.ps1 b/tests/chocolatey-tests/commands/choco-deprecated.Tests.ps1 similarity index 92% rename from tests/chocolatey-tests/choco-deprecated.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-deprecated.Tests.ps1 index 0c7694aec1..1ef836046f 100644 --- a/tests/chocolatey-tests/choco-deprecated.Tests.ps1 +++ b/tests/chocolatey-tests/commands/choco-deprecated.Tests.ps1 @@ -1,6 +1,15 @@ Import-Module helpers/common-helpers Describe "choco deprecated shims" -Skip:(-not (Test-ChocolateyVersionEqualOrHigherThan "1.0.0")) -Tag Chocolatey, DeprecatedShims { + BeforeAll { + Initialize-ChocolateyTestInstall + New-ChocolateyInstallSnapshot + } + + AfterAll { + Remove-ChocolateyTestInstall + } + BeforeDiscovery { $DeprecatedShims = @( @{ Command = 'help'; Deprecation = 'none' } @@ -60,6 +69,7 @@ Describe "Deprecated Chocolatey Helper Commands" -Skip:(-not (Test-ChocolateyVer AfterAll { Remove-Item "./$PackageName" -Recurse -Force -ErrorAction Ignore + Remove-ChocolateyTestInstall } It 'should not mention Get-BinRoot in any of the generated files' { $TemplateOutput | Should -BeNullOrEmpty -Because 'Get-BinRoot has been deprecated and removed from the template' diff --git a/tests/chocolatey-tests/choco-export.Tests.ps1 b/tests/chocolatey-tests/commands/choco-export.Tests.ps1 similarity index 100% rename from tests/chocolatey-tests/choco-export.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-export.Tests.ps1 diff --git a/tests/chocolatey-tests/choco-feature.Tests.ps1 b/tests/chocolatey-tests/commands/choco-feature.Tests.ps1 similarity index 100% rename from tests/chocolatey-tests/choco-feature.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-feature.Tests.ps1 diff --git a/tests/chocolatey-tests/choco-help.Tests.ps1 b/tests/chocolatey-tests/commands/choco-help.Tests.ps1 similarity index 91% rename from tests/chocolatey-tests/choco-help.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-help.Tests.ps1 index a3180aaacd..f8e01f256c 100644 --- a/tests/chocolatey-tests/choco-help.Tests.ps1 +++ b/tests/chocolatey-tests/commands/choco-help.Tests.ps1 @@ -28,6 +28,12 @@ Describe "choco help sections with command <_>" -ForEach $Command -Tag Chocolate BeforeAll { $helpArgument = $_ + Initialize-ChocolateyTestInstall + New-ChocolateyInstallSnapshot + } + + AfterAll { + Remove-ChocolateyTestInstall } Context "Top Level Help" { diff --git a/tests/chocolatey-tests/choco-info.Tests.ps1 b/tests/chocolatey-tests/commands/choco-info.Tests.ps1 similarity index 84% rename from tests/chocolatey-tests/choco-info.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-info.Tests.ps1 index 8b8a62d22b..1356d18aa9 100644 --- a/tests/chocolatey-tests/choco-info.Tests.ps1 +++ b/tests/chocolatey-tests/commands/choco-info.Tests.ps1 @@ -142,4 +142,22 @@ Describe "choco info" -Tag Chocolatey, InfoCommand { $Output.Lines | Should -Contain "${Title}: $Value" } } + + Context "Listing package information about local side by side installed package" { + BeforeAll { + Restore-ChocolateyInstallSnapshot + $null = Invoke-Choco install 'isdependency' --confirm --sxs + + $Output = Invoke-Choco info 'isdependency' --local-only + } + + It "Exits with Success (0)" { + $Output.ExitCode | Should -Be 0 + } + + It "Outputs a warning message that installed side by side package is deprecated" { + $Output.Lines | Should -Contain "isdependency has been installed as a side by side installation." -Because $Output.String + $Output.Lines | Should -Contain "Side by side installations are deprecated and is pending removal in v2.0.0." -Because $Output.String + } + } } diff --git a/tests/chocolatey-tests/choco-install.Tests.ps1 b/tests/chocolatey-tests/commands/choco-install.Tests.ps1 similarity index 82% rename from tests/chocolatey-tests/choco-install.Tests.ps1 rename to tests/chocolatey-tests/commands/choco-install.Tests.ps1 index 792eb2d621..79825f92ab 100644 --- a/tests/chocolatey-tests/choco-install.Tests.ps1 +++ b/tests/chocolatey-tests/commands/choco-install.Tests.ps1 @@ -199,6 +199,123 @@ Describe "choco install" -Tag Chocolatey, InstallCommand { It "Outputs a message indicating that missingpackage was not installed" { $Output.Lines | Should -Contain "missingpackage not installed. The package was not found with the source(s) listed." } + + Context "packages.config containing all options" { + BeforeAll { + @" + + + + +"@ | Out-File $env:CHOCOLATEY_TEST_PACKAGES_PATH\alloptions.packages.config -Encoding utf8 + + $Output = Invoke-Choco install $env:CHOCOLATEY_TEST_PACKAGES_PATH\alloptions.packages.config --confirm --verbose --debug + + # This is based on two observations: The addition explicitly outputs that it's the Package Configuration. + # The configuration output is also about 80 lines. + $StartofPackageConfiguration = [array]::IndexOf($Output.Lines, "Package Configuration: CommandName='install'|") + $PackageConfigurationOutput = $Output.Lines[$StartofPackageConfiguration..($StartofPackageConfiguration+80)] -join [Environment]::NewLine + } + + # We are explicitly passing in a bad username and password here. + # Therefore it cannot find the package to install and fails the install. + # That doesn't matter because we just need to test that the configuration is set properly. + It "Should exit Failure (1)" { + $Output.ExitCode | Should -Be 1 -Because $Output.String + } + + It "Should contain the expected configuration option (