diff --git a/.build.custom/ilmerge.internalize.ignore.txt b/.build.custom/ilmerge.internalize.ignore.txt index 8c1842244d..120f0425b2 100644 --- a/.build.custom/ilmerge.internalize.ignore.txt +++ b/.build.custom/ilmerge.internalize.ignore.txt @@ -1,3 +1,6 @@ chocolatey.* NuGet.Manifest -NuGet.IPackage \ No newline at end of file +NuGet.IPackage +NuGet.PackageDependency +NuGet.IServerPackageMetadata +NuGet.SemanticVersion \ No newline at end of file diff --git a/.build.custom/ilmerge.replace.build b/.build.custom/ilmerge.replace.build index 9dcb927ad5..11786707ad 100644 --- a/.build.custom/ilmerge.replace.build +++ b/.build.custom/ilmerge.replace.build @@ -25,8 +25,8 @@ - - + + @@ -39,8 +39,8 @@ - - + + diff --git a/.build.custom/ilmergeDLL.build b/.build.custom/ilmergeDLL.build index e12511e3b9..14a52be43a 100644 --- a/.build.custom/ilmergeDLL.build +++ b/.build.custom/ilmergeDLL.build @@ -26,8 +26,8 @@ - - + + - - + + @@ -38,8 +38,8 @@ - - + + diff --git a/.uppercut b/.uppercut index 12c4a6554c..d2fbe2f473 100644 --- a/.uppercut +++ b/.uppercut @@ -17,10 +17,10 @@ - - - - + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index baf5f4cb1d..6a7dc496fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## [0.9.10](https://github.com/chocolatey/choco/issues?q=milestone%3A0.9.10+is%3Aclosed) (unreleased) + +Alternative sources (webpi, windowsfeature, cygwin, etc) are back (finally, right?)! + +### FEATURES + + * Alternative sources - see [#14](https://github.com/chocolatey/choco/issues/14) + * Support for custom headers - see [#332](https://github.com/chocolatey/choco/issues/332) + +### BUG FIXES + + * Fix - Merging assemblies on a machine running .net 4.5 or higher produces binaries incompatible with .net 4 - see [#392](https://github.com/chocolatey/choco/issues/392) + * Fix - API - Incorrect log4net version in chocolatey.lib dependencies - see [#390](https://github.com/chocolatey/choco/issues/390) + +### IMPROVEMENTS + + * AutoUninstaller is on by default - see [#308](https://github.com/chocolatey/choco/issues/308) + * Show human-readable file sizes when downloading - see [#363](https://github.com/chocolatey/choco/issues/363) + * API - Add the ability to retrieve package count for a Source - see [#431](https://github.com/chocolatey/choco/issues/431) + * API - Chocolatey Lib still marks vital package information as internal - see [#433](https://github.com/chocolatey/choco/issues/433) + * API - Add paging to list command - see [#427](https://github.com/chocolatey/choco/issues/427) + ## [0.9.9.11](https://github.com/chocolatey/choco/issues?q=milestone%3A0.9.9.11+is%3Aclosed) (October 6, 2015) ### BUG FIXES diff --git a/lib/NuGet-Chocolatey/strongname.cmd b/lib/NuGet-Chocolatey/strongname.cmd index c902585d6a..a96c23d52b 100644 --- a/lib/NuGet-Chocolatey/strongname.cmd +++ b/lib/NuGet-Chocolatey/strongname.cmd @@ -7,4 +7,4 @@ SET DIR=%~d0%~p0% echo Make sure you have webtransform here for the merge. Continue? pause -%DIR%..\ILMerge\ILMerge.exe NuGet.Core.dll /keyfile:%DIR%..\..\chocolatey.snk /out:%DIR%NuGet.Core.dll /targetplatform:v4 /log:%DIR%ILMerge.DELETE.log /ndebug /allowDup \ No newline at end of file +%DIR%..\ILMerge\ILMerge.exe NuGet.Core.dll /keyfile:%DIR%..\..\chocolatey.snk /out:%DIR%NuGet.Core.dll /targetplatform:v4,"%ProgramFiles(x86)%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0" /log:%DIR%ILMerge.DELETE.log /ndebug /allowDup \ No newline at end of file diff --git a/nuget/chocolatey.lib/chocolatey.lib.nuspec b/nuget/chocolatey.lib/chocolatey.lib.nuspec index 126062ab19..7357b7e3be 100644 --- a/nuget/chocolatey.lib/chocolatey.lib.nuspec +++ b/nuget/chocolatey.lib/chocolatey.lib.nuspec @@ -22,7 +22,7 @@ This is the core package which allows Chocolatey to be embedded in your applicat apt-get yum machine repository chocolatey https://github.com/chocolatey/chocolatey/raw/master/docs/logo/chocolateyicon.gif - + diff --git a/nuget/chocolatey/chocolatey.nuspec b/nuget/chocolatey/chocolatey.nuspec index 469751c349..0bc3ec1f72 100644 --- a/nuget/chocolatey/chocolatey.nuspec +++ b/nuget/chocolatey/chocolatey.nuspec @@ -17,14 +17,16 @@ You can host your own sources and add them to Chocolatey, you can extend Chocola ### Commands There are quite a few commands you can call - you should check out the [command reference](https://github.com/chocolatey/choco/wiki/CommandsReference). Here are the most common: + * Help - choco -? or choco command -? * Search - choco search something * List - choco list -lo + * Config - choco config list * Install - choco install baretail * Pin - choco pin windirstat * Upgrade - choco upgrade baretail * Uninstall - choco uninstall baretail - Alternative installation sources (TEMPORARILY DISABLED IN 0.9.9 series): + Alternative installation sources: * Install ruby gem - choco install compass -source ruby * Install python egg - choco install sphynx -source python * Install windows feature - choco install IIS -source windowsfeatures @@ -40,317 +42,28 @@ In that mess there is a link to the [Helper Reference](https://github.com/chocol See all - https://github.com/chocolatey/choco/blob/master/CHANGELOG.md -## 0.9.9.11 +## 0.9.10 -### BUG FIXES - - * Fix - Pin list is broken - see [#452](https://github.com/chocolatey/choco/issues/452) - -## 0.9.9.10 - -Not to be confused with 0.9.10 (this is not that version). This fixes a small but extremely significant issue with relation to configuration managers and other tools that use choco. - -### BUG FIXES - - * Fix - List output for other tools messed up in 0.9.9.9 (pipe separator missing) - see [#450](https://github.com/chocolatey/choco/issues/450) - * Fix - accidentally escaped characters in "new" -help - see [#447](https://github.com/chocolatey/choco/issues/447) - -## 0.9.9.9 - -With this release you can completely configure choco from the command line (including the priority of sources). Choco now allows you to create [custom package templates](https://github.com/chocolatey/choco/issues/76). Choco has [proper proxy support](https://github.com/chocolatey/choco/issues/243) now. We also squashed up some bugs, like the infinite download loop that happens if the connection is lost. We've also improved the installation experience of Chocolatey itself, [unpacking all of the required setup files in the chocolatey package](https://github.com/chocolatey/choco/issues/347) and improving the messaging output during the bootstrapping process. Chocolatey also [doesn't try to write config updates every command](https://github.com/chocolatey/choco/issues/364), unless something actually changes in the config file. And last but not least for mentions, the issue of [choco not recognizing itself as needing upgraded after being installed by the bootstrapper](https://github.com/chocolatey/choco/issues/414) is now fixed. - -### FEATURES - - * Config Command - see [#417](https://github.com/chocolatey/choco/issues/417) - * Create Custom Package Templates - see [#76](https://github.com/chocolatey/choco/issues/76) - * Proxy Support - see [#243](https://github.com/chocolatey/choco/issues/243) - -### BUG FIXES - - * Fix - [Security] Remove rollback should validate it exists in choco install backup directory - see [#387](https://github.com/chocolatey/choco/issues/387) - * Fix - Ensure chocolatey is installed into the lib folder during initial install - see [#414](https://github.com/chocolatey/choco/issues/414) - * Fix - Infinite loop downloading files if connection is lost - see [#285](https://github.com/chocolatey/choco/issues/285) - * Fix - list / search results blocking until completion instead of streaming output - see [#143](https://github.com/chocolatey/choco/issues/143) - * Fix - default template install script for MSI silentArgs are bad - see [#354](https://github.com/chocolatey/choco/issues/354) - * Fix - Deleting read-only files fails - see [#338](https://github.com/chocolatey/choco/issues/338) and [#263](https://github.com/chocolatey/choco/issues/263) - * Fix - If the package uses $packageParameters instead of $env:PackageParameters, quotes are removed - see [#406](https://github.com/chocolatey/choco/issues/406) - * Fix - Choco upgrade not downloading new installer if current installer is the same size - see [#405](https://github.com/chocolatey/choco/issues/405) - * Fix - Exit with non-zero code if install/upgrade version and a newer version is installed - see [#365](https://github.com/chocolatey/choco/issues/365) - * Fix - Chocolately can permanently corrupt the config file if an operation is interrupted - see [#355](https://github.com/chocolatey/choco/issues/355) - * Fix - Handle PowerShell's `InitializeDefaultDrives` Error (that should just be a warning) - see [#349](https://github.com/chocolatey/choco/issues/349) - * Fix - Checksumming can not be turned off by the feature flag - see [#33](https://github.com/chocolatey/choco/issues/33) - * Fix - Process with an id of is not running errors on 0.9.9.8 - see [#346](https://github.com/chocolatey/choco/issues/346) - * Fix - Export cmdlets for automation scripts - see [#422](https://github.com/chocolatey/choco/issues/422) - -### IMPROVEMENTS - - * [Security] Add SHA-2 (sha256 / sha512) to checksum - see [#113](https://github.com/chocolatey/choco/issues/113) - * Sources should have explicit priority order- see [#71](https://github.com/chocolatey/choco/issues/71) - * Unpack the powershell files just before packaging up the nupkg (Installing chocolatey meta) - see [#347](https://github.com/chocolatey/choco/issues/347) - * API - List --localonly not working by default - see [#223](https://github.com/chocolatey/choco/issues/223) - * API - Expose package results - see [#132](https://github.com/chocolatey/choco/issues/132) - * API - Externalize IPackage and its interfaces - see [#353](https://github.com/chocolatey/choco/issues/353) - * Enhance "Access to path is denied" message on no admin rights - see [#177](https://github.com/chocolatey/choco/issues/177) - * Only update chocolatey.config if there are changes - see [#364](https://github.com/chocolatey/choco/issues/364) - * Modify source when attempting to add a source with same name but different URL - see [#88](https://github.com/chocolatey/choco/issues/88) - * Features should contain description - see [#416](https://github.com/chocolatey/choco/issues/416) - * Chocolatey Installer - removing modules not loaded - see [#442](https://github.com/chocolatey/choco/issues/442) - * Chocolatey Installer - Don't use Write-Host - see [#444](https://github.com/chocolatey/choco/issues/444) - * Set environment variables once configuration is complete - see [#420](https://github.com/chocolatey/choco/issues/420) - * Enhance Package Template for 0.9.9.9 - see [#366](https://github.com/chocolatey/choco/issues/366) - -## 0.9.9.8 - -### BUG FIXES - - * Fix: choco install -y C: deletes all files - see [#341](https://github.com/chocolatey/choco/issues/341) - * Fix: Read-Host halts scripts rather than prompt for input - see [#219](https://github.com/chocolatey/choco/issues/219) - -### IMPROVEMENTS - - * Download Progress Bar is Missing - see [#56](https://github.com/chocolatey/choco/issues/56) - -## 0.9.9.7 - -"Fix Everything. Fix All The Things" - There have been some things bugging us for a long time related to limitations with NuGet, so we decided to fix that. Like [nuspec enhancements](https://github.com/chocolatey/choco/issues/205), that crazy [content folder restriction](https://github.com/chocolatey/choco/issues/290) has been removed (I know, right?!), and we're working around [badly](https://github.com/chocolatey/choco/issues/316) [behaved](https://github.com/chocolatey/choco/issues/326) packages quite a bit more to bring you more feature parity. - -Let's talk about a couple of big, like really big, BIG features just added with this release. No more packages rebooting Windows. We fixed ([#304](https://github.com/chocolatey/choco/issues/304) / [#323](https://github.com/chocolatey/choco/issues/323)) and [enhanced](https://github.com/chocolatey/choco/issues/305) up the Auto Uninstaller Service quite a bit to ensure things are working like you would expect (It goes on by default in 0.9.10 - we'll start documenting more about it soon). But wait, there's more! I haven't even told you about the big features yet - -The first big feature is enhancing the nuspec. I mentioned this I know, but *now* you can use `packageSourceUrl` in the nuspec to tell folks where you are storing the source for the package! We also added `projectSourceUrl`, `docsUrl`, `mailingListUrl`, and `bugTrackerUrl`. What's even better is that the community feed has already been enhanced to look for these values. So have the templates from `choco new`. And it's backwards compatible, meaning you can still install packages that have these added nuspec enhancements without issue (but we will need to provide a fix for Nuget Package Explorer). - -The second is Xml Document Transformations (XDT), which I think many folks are aware of but may not realize what it can provide. [NuGet has allowed transformations for quite awhile](https://docs.nuget.org/Create/Configuration-File-and-Source-Code-Transformations) to allow you to make changes to an `app.config`/`web.config` on install/uninstall. We are following in similar footsteps to allow you to do similar when installing/upgrading packages. We will look for `*.install.xdt` files in the package (doesn't matter where) and they will apply to configuration files with the same name in the package. This means that during upgrades we won't overwrite configuration files during upgrades that have opted into this feature. It allows you to give users a better experience during upgrades because they won't need to keep making the same changes to the xml config files each time they upgrade your package. - -### FEATURES - - * Allow XDT Configuration Transforms - see [#331](https://github.com/chocolatey/choco/issues/331) - * Prevent reboots - see [#316](https://github.com/chocolatey/choco/issues/316) - * Enhance the nuspec - first wave - see [#205](https://github.com/chocolatey/choco/issues/205) - * Uninstaller Service Enhancements - see [#305](https://github.com/chocolatey/choco/issues/305) - -### BUG FIXES - - * When uninstall fails, do not continue removing files - see [#315](https://github.com/chocolatey/choco/issues/315) - * Do not run autouninstaller if the package result is already a failure - see [#323](https://github.com/chocolatey/choco/issues/323) - * Fix - Auto Uninstaller can fail if chocolateyUninstall.ps1 uninstalls prior to it running - see [#304](https://github.com/chocolatey/choco/issues/304) - * Fix - Packages with content folders cannot have a dependency without also having a content folder - see [#290](https://github.com/chocolatey/choco/issues/290) - * Remove ShimGen director files on upgrade/uninstall - see [#326](https://github.com/chocolatey/choco/issues/326) - * If feature doesn't exist, throw an error - see [#317](https://github.com/chocolatey/choco/issues/317) - * Fix - The operation completed successfully on stderr - see [#249](https://github.com/chocolatey/choco/issues/249) - * Fix - When specific nuget version is needed by a package it is the chocolatey version that is used - see [#194](https://github.com/chocolatey/choco/issues/194) - * When installing with *.nupkg, need to get package name from package, not file name - see [#90](https://github.com/chocolatey/choco/issues/90) - * Fix - Choco pin list is not returning a list - see [#302](https://github.com/chocolatey/choco/issues/302) - * Fix - A pin is not created for existing installations (prior to new choco) - see [#60](https://github.com/chocolatey/choco/issues/60) - -### IMPROVEMENTS - - * Allow upgrade to always install missing packages - see [#300](https://github.com/chocolatey/choco/issues/300) - * Enhance Templates - see [#296](https://github.com/chocolatey/choco/issues/296) - * Always log debug output to the log file - see [#319](https://github.com/chocolatey/choco/issues/319) - * Warn when unable to snapshot locked files - see [#313](https://github.com/chocolatey/choco/issues/313) - * Use %systemroot% in place of %windir%. PATH exceed 2048 breaks choco - see [#252](https://github.com/chocolatey/choco/issues/252) - -## 0.9.9.6 - -Some really large fixes this release, especially removing all files that are installed to the package directory if they haven't changed, including ensuring that the nupkg file is always removed on successful uninstalls. The really big add some folks are going to like is the new outdated command. Some more variables that were misused have been brought back, which allows some packages (like Atom) to be installed again without issue. If you can believe some people never read these, we decided to add a note to the installer prompt to let people know about -y. - -### FEATURES - - * Outdated Command - Use `choco outdated` to see outdated packages - see [#170](https://github.com/chocolatey/choco/issues/170) - -### BUG FIXES - - * Fix - NotSilent Switch Not Working - see [#281](https://github.com/chocolatey/choco/issues/281) - * Fix - Silent installation of choco without admin is not possible - see [#274](https://github.com/chocolatey/choco/issues/274) - * Fix - Package resolves to latest version from any source - see [#279](https://github.com/chocolatey/choco/issues/279) - * Fix - Install fails when shortcut creation fails - see [#264](https://github.com/chocolatey/choco/issues/264) - * Fix - Error deserializing response of type Registry - see [#257](https://github.com/chocolatey/choco/issues/257) - * Fix - Auto uninstaller should not depend on optional InstallLocation value - see [#255](https://github.com/chocolatey/choco/issues/255) - * Fix - Nupkg is left but reported as successfully uninstalled by NuGet - see [#254](https://github.com/chocolatey/choco/issues/254) - * Fix - SHA1 checksum compared as MD5 for Install-ChocolateyZipPackage - see [#253](https://github.com/chocolatey/choco/issues/253) - * Fix - Auto uninstaller strips off "/" and "-" in arguments - see [#212](https://github.com/chocolatey/choco/issues/212) -### IMPROVEMENTS - - * Uninstall removes all installed files if unchanged - see [#121](https://github.com/chocolatey/choco/issues/121) - * Auto uninstaller should convert /I to /X for Msi Uninstalls - see [#271](https://github.com/chocolatey/choco/issues/271) - * Bring back more variables for feature parity - see [#267](https://github.com/chocolatey/choco/issues/267) - * Mention -y in the prompt - see [#265](https://github.com/chocolatey/choco/issues/265) - -## 0.9.9.5 - -### BREAKING CHANGES - - * Renamed short option `p` to `i` for list --include-programs so that `p` could be ubiquitous for password across commands that optionally can pass a password - see [#240](https://github.com/chocolatey/choco/issues/240) - -### BUG FIXES - - * Fix - Secure Sources Not Working - see [#240](https://github.com/chocolatey/choco/issues/240) - * Fix - Generate-BinFile / Remove-BinFile - see [#230](https://github.com/chocolatey/choco/issues/230) - * Fix - cpack should only include files from nuspec - see [#232](https://github.com/chocolatey/choco/issues/232) - * Fix - cpack should leave nupkg in current directory - see [#231](https://github.com/chocolatey/choco/issues/231) - * Fix - Install-PowerShellCommand uses incorrect path - see [#241](https://github.com/chocolatey/choco/issues/241) - * Fix - choco list source with redirects does not resolve - see [#171](https://github.com/chocolatey/choco/issues/171) - * Fix - choco tried to resolve disabled repo - see [#169](https://github.com/chocolatey/choco/issues/169) - * Fix - cpack nuspec results in "The path is not of a legal form" - see [#164](https://github.com/chocolatey/choco/issues/164) - * Fix - cpack hangs on security related issue - see [#160](https://github.com/chocolatey/choco/issues/160) - * Fix - spelling error in "package has been upgradeed successfully" - see [#64](https://github.com/chocolatey/choco/issues/64) - -### IMPROVEMENTS - - * Api Key and Source matching could be more intuitive - see [#228](https://github.com/chocolatey/choco/issues/238) - * Remove warning about allowGlobalConfirmation being enabled - see [#237](https://github.com/chocolatey/choco/issues/237) - * Include log file path when saying 'See the log for details' - see [#187](https://github.com/chocolatey/choco/issues/187) - * Uninstall prompts for version when there is only one installed - see [#186](https://github.com/chocolatey/choco/issues/186) - * Do not offer a default option when prompting for a user choice - see [#185](https://github.com/chocolatey/choco/issues/185) - * Remove the warning note about skipping, and instead show the warning when selecting skip - see [#183](https://github.com/chocolatey/choco/issues/183) - * Do not print PowerShell install/update scripts by default - see [#182](https://github.com/chocolatey/choco/issues/182) - -## 0.9.9.4 - -### BUG FIXES - - * Fix - The term 'false' is not recognized as the name of a cmdlet - see [#215](https://github.com/chocolatey/choco/issues/215) - -### IMPROVEMENTS - - * Some packages use non-API variables like $installArguments - see [#207](https://github.com/chocolatey/choco/issues/207) - -## 0.9.9.3 - -### BUG FIXES - - * Fix - Install .NET Framework immediately during install - see [#168](https://github.com/chocolatey/choco/issues/168) - * Fix - Do not error on Set-Acl during install/upgrade - see [#163](https://github.com/chocolatey/choco/issues/163) - * Fix - Do not escape curly braces in powershell script - see [#208](https://github.com/chocolatey/choco/issues/208) - * Fix - Formatting issues on --noop command logging - see [#202](https://github.com/chocolatey/choco/issues/202) - * Fix - Uninstaller check doesn't find 32-bit registry keys - see [#197](https://github.com/chocolatey/choco/issues/197) - * Fix - Uninstaller errors on short path to msiexec - see [#211](https://github.com/chocolatey/choco/issues/211) - -### IMPROVEMENTS - - * Some packages use non-API variables like $installArguments - see [#207](https://github.com/chocolatey/choco/issues/207) - * Add Generate-BinFile to Helpers (widely used but never part of API) - see [#145](https://github.com/chocolatey/choco/issues/145) - * Add Remove-BinFile to Helpers - see [#195](https://github.com/chocolatey/choco/issues/195) - * Get-ChocolateyWebFile should create path if it doesn't exist - see [#167](https://github.com/chocolatey/choco/issues/167) - -## 0.9.9.2 - -### BUG FIXES - - * Fix - Allow passing install arguments again (regression in 0.9.9 series) - see [#150](https://github.com/chocolatey/choco/issues/150) - * Fix - Allow apostrophes to be used as quotes - quoting style that worked with previous client - see [#141](https://github.com/chocolatey/choco/issues/141) - * Fix - Shims write errors to stderr - see [#142](https://github.com/chocolatey/choco/issues/142) and [ShimGen #14](https://github.com/chocolatey/shimgen/issues/14) - -### IMPROVEMENTS - - * Upgrade `-r` should always return a value - see [#153](https://github.com/chocolatey/choco/issues/153) - -## 0.9.9.1 - -### BUG FIXES - - * Fix - Get-BinRoot broken - see [#144](https://github.com/chocolatey/choco/issues/144) - -##0.9.9 - -This also includes issues that were being tracked in the old Chocolatey repository: [Chocolatey 0.9.9](https://github.com/chocolatey/chocolatey/issues?q=is%3Aclosed+label%3Av0.9.9). - -The two links above will not capture everything that has changed, since this is a complete rewrite. We broke everything. If this were a v1+, it would be a major release. But we are less than v1, so 0.9.9 it is! ;) - -Okay, so we didn't really break everything. We have maintained nearly full compatibility with how you pass options into choco, although the output may be a bit different (but better, we hope) and in at least one case, additional switches (or a feature setting) is/are required - we limited this to security related changes only. - -We also fixed and improved a bunch of things, so we feel the trade off is well worth the changes. - -We'll try to capture everything here that you should know about. Please call `choco -?` or `choco.exe -h` to get started. - -### KNOWN ISSUES - - * [Known Issues](https://github.com/chocolatey/choco/labels/Bug) - * TEMPORARY `install all` is missing - this is expected to be back in 0.9.10 - see [#23](https://github.com/chocolatey/choco/issues/23) - * Alternative sources (`webpi`,`ruby`,`python`,`cygwin`, `windowsfeature`) do not work yet. This is expected to be fixed in 0.9.10 - see [#14](https://github.com/chocolatey/choco/issues/14) - * Progress bar is missing when downloading until we are using internal posh components for Packages - see [#56](https://github.com/chocolatey/choco/issues/56) - * See [Feature Parity](https://github.com/chocolatey/choco/labels/FeatureParity) for items not yet reimplemented from older PowerShell Chocolatey client (v0.9.8.32 and below). - -### BREAKING CHANGES - - * [Security] **Prompt for confirmation**: For security reasons, we now stop for confirmation before changing the state of the system on most commands. You can pass `-y` to confirm any prompts or set a value in the config that will globally confirm - see [#52](https://github.com/chocolatey/choco/issues/52) (**NOTE**: This is one of those additional switches we were talking about) - * [Security] If your default installation is still at `c:\Chocolatey`, this version will force a move to ProgramData and update the environment settings - see [#7](https://github.com/chocolatey/choco/issues/7) - * **Configuration Breaking Changes:** - 1. You now have one config file to interact with in %ChocolateyInstall%\config - your user config is no longer valid and can be removed once you migrate settings to the config. - 2. The config will no longer be overwritten on upgrade. - 3. Choco no longer interacts with NuGet's config file at all. You will need to reset all of your apiKeys (see features for `apikey`). On the plus side, the keys will work for all users of the machine, unlike NuGet's apiKeys (only work for the user that sets them). - 4. This also means you can no longer use `useNugetForSources`. It has been removed as a config setting. - * **Packaging Changes:** - 1. Choco now installs packages without version numbers on folders. This means quite a few things... - 2. Upgrading packages doesn't install a new version next to an old version, it actually upgrades. - 3. Dependencies resolve at highest available version, not the minimum version as before - see [Chocolatey #415](https://github.com/chocolatey/chocolatey/issues/415) - * **Package Maintenance Changes**: - 1. Read the above about apikey changes - 2. Read above about dependency resolution changes. - * **Deprecated/Removed Commands:** - 1. `installmissing` has been removed. It was deprecated awhile ago, so this should not be a surprise. - 2. `choco version` has been deprecated and will be removed in v1. Use `choco upgrade pkgName --noop` or `choco upgrade pkgName -whatif` instead. - 3. `Write-ChocolateySuccess`, `Write-ChocolateyFailure` have been deprecated. - 4. `update` is now `upgrade`. `update` has been deprecated and will be removed/replaced in v1. Update will be reincarnated later for a different purpose. **Hint**: It rhymes with smackage pindexes. +Alternative sources (webpi, windowsfeature, cygwin, etc) are back (finally, right?)! ### FEATURES - * In app documentation! Use `choco -?`, `choco -h` or `choco commandName -?` to learn about each command, complete with examples! - * WhatIf/Noop mode for all commands (`--noop` can also be specified as `-whatif`) - see [Chocolatey #263](https://github.com/chocolatey/chocolatey/issues/263) and [Default Options and Switches](https://github.com/chocolatey/choco/wiki/CommandsReference#default-options-and-switches) - * Performs like a package manager, expect to see queries failing because of unmet dependency issues. - * **New Commands:** - 1. `pin` - Suppress upgrades. This allows you to 'pin' an install to a particular version - see [#1](https://github.com/chocolatey/choco/issues/1), [Chocolatey #5](https://github.com/chocolatey/chocolatey/issues/5) and [Pin Command](https://github.com/chocolatey/choco/wiki/CommandsPin) - 2. `apikey` - see [ApiKey Command](https://github.com/chocolatey/choco/wiki/CommandsApiKey) - 3. `new` - see [New Command](https://github.com/chocolatey/choco/wiki/CommandsNew) and [Chocolatey #157](https://github.com/chocolatey/chocolatey/issues/157) - * New ways to pass arguments! See [How to Pass Options/Switches](https://github.com/chocolatey/choco/wiki/CommandsReference#how-to-pass-options--switches) - * Did we mention there is a help menu that is actually helpful now? Shiny! - * AutoUninstaller!!!! But it is not enabled by default this version. See [#15](https://github.com/chocolatey/choco/issues/15), [#9](https://github.com/chocolatey/choco/issues/9) and [Chocolatey #6](https://github.com/chocolatey/chocolatey/issues/6) - * **New Helpers:** - 1. `Install-ChocolateyShortcut` - see [Chocolatey #238](https://github.com/chocolatey/chocolatey/pull/238), [Chocolatey #235](https://github.com/chocolatey/chocolatey/issues/235) and [Chocolatey #218](https://github.com/chocolatey/chocolatey/issues/218) + * Alternative sources - see [#14](https://github.com/chocolatey/choco/issues/14) + * Support for custom headers - see [#332](https://github.com/chocolatey/choco/issues/332) ### BUG FIXES -Probably a lot of bug fixes that may not make it here, but here are the ones we know about. - - * Fix - Cannot upgrade from prerelease to same version released - see [Chocolatey #122](https://github.com/chocolatey/chocolatey/issues/122) - * Fix - install `--force` should not use cache - see [Chocolatey #199](https://github.com/chocolatey/chocolatey/issues/199) - * Fix - force dependencies as well - see [--force-dependencies](https://github.com/chocolatey/choco/wiki/CommandsInstall) and [Chocolatey #199](https://github.com/chocolatey/chocolatey/issues/199) - * Fix - Chocolatey should not stop on error - see [Chocolatey #192](https://github.com/chocolatey/chocolatey/issues/192) - * Fix - Upgrading does not remove previous version - see [Chocolatey #259](https://github.com/chocolatey/chocolatey/issues/259) - * Fix - Non-elevated shell message spills errors - see [Chocolatey #540](https://github.com/chocolatey/chocolatey/issues/540) - * Fix - Package names are case sensitive for some sources - see [Chocolatey #589](https://github.com/chocolatey/chocolatey/issues/589) - * Fix - Install-ChocolateyVsixPackage doesn't check for correct VS 2012 path - see [Chocolatey #601](https://github.com/chocolatey/chocolatey/issues/601) - * Fix - Chocolatey behaves strangely after ctrl+c - see [Chocolatey #608](https://github.com/chocolatey/chocolatey/issues/608) - * Fix - Uninstall doesn't respect version setting - see [Chocolatey #612](https://github.com/chocolatey/chocolatey/issues/612) - * Fix - No update after download error - see [Chocolatey #637](https://github.com/chocolatey/chocolatey/issues/637) - * Fix - cup ends silently on error - see [Chocolatey #312](https://github.com/chocolatey/chocolatey/issues/312) - * Fix - cpack silently fails when dependency .NET 4.0+ is not met - see [Chocolatey #270](https://github.com/chocolatey/chocolatey/issues/270) - * Fix - Regression in cver all in 0.9.8.27 - see [Chocolatey #530](https://github.com/chocolatey/chocolatey/issues/530) - * Fix - Certain installs and updates fail with a "process with an Id of xxxx is not running" error - see [Chocolatey #603](https://github.com/chocolatey/chocolatey/issues/603) + * Fix - Merging assemblies on a machine running .net 4.5 or higher produces binaries incompatible with .net 4 - see [#392](https://github.com/chocolatey/choco/issues/392) + * Fix - API - Incorrect log4net version in chocolatey.lib dependencies - see [#390](https://github.com/chocolatey/choco/issues/390) ### IMPROVEMENTS - * [Security] Allow keeping `c:\chocolatey` install directory with environment variable - see [#17](https://github.com/chocolatey/choco/issues/17) - * [Security] Require switch on unofficial build - see [#36](https://github.com/chocolatey/choco/issues/36) - * Install script updates - see [#7](https://github.com/chocolatey/choco/issues/7) - * Ensure Chocolatey pkg is installed properly in lib folder - This means you can take a dependency on a minimum version of Chocolatey (we didn't like that before) - see [#19](https://github.com/chocolatey/choco/issues/19) - * Uninstall - allow abort - see [#43](https://github.com/chocolatey/choco/issues/43) - * Support for HTTPS basic authorization - see [Chocolatey #128](https://github.com/chocolatey/chocolatey/issues/128) - * Smooth out success/failure logging - see [Chocolatey #154](https://github.com/chocolatey/chocolatey/issues/154) - * Add $env:CHOCOLATEY_VERSION - see [Chocolatey #251](https://github.com/chocolatey/chocolatey/issues/251) - * Replace ascii cue with visual cues - see [Chocolatey #376](https://github.com/chocolatey/chocolatey/pull/376) - * Uninstall all versions of an app - see [Chocolatey #389](https://github.com/chocolatey/chocolatey/issues/389) - * Add parameters in packages.config files - see [Packages.config](https://github.com/chocolatey/choco/wiki/CommandsInstall#packagesconfig), [Chocolatey #472](https://github.com/chocolatey/chocolatey/issues/472), and [#10](https://github.com/chocolatey/choco/issues/10) - * Choco pack should support `-version` - see [Chocolatey #526](https://github.com/chocolatey/chocolatey/issues/526) - * Enhancements to Start-ChocolateyProcessAsAdmin - see [Chocolatey #564](https://github.com/chocolatey/chocolatey/pull/564) - * Install-ChocolateyFileAssociation - add label to new file types - see [Chocolatey #564](https://github.com/chocolatey/chocolatey/pull/564) - * Clean up the verobsity of Chocolatey - see [Chocolatey #374](https://github.com/chocolatey/chocolatey/issues/374) - * Compact choco upgrade --noop option - see [Chocolatey #414](https://github.com/chocolatey/chocolatey/issues/414) - * Remove references to the Chocolatey gods - see [Chocolatey #669](https://github.com/chocolatey/chocolatey/issues/669) - * Shims now have noop (`--shimgen-noop`) and help (`--shimgen-help`) switches - see [ShimGen #8](https://github.com/chocolatey/shimgen/issues/8) and [ShimGen #10](https://github.com/chocolatey/shimgen/issues/10) - * Shims will terminate underlying process on termination signal - see [ShimGen #11](https://github.com/chocolatey/shimgen/issues/11) - * Shims now have gui (`--shimgen-gui`) and exit (`--shimgen-exit`) switches - see [ShimGen #13](https://github.com/chocolatey/shimgen/issues/13) and [ShimGen #12](https://github.com/chocolatey/shimgen/issues/12) - * Dat help menu tho. I mean srsly guise - see [Chocolatey #641](https://github.com/chocolatey/chocolatey/issues/641) + * AutoUninstaller is on by default - see [#308](https://github.com/chocolatey/choco/issues/308) + * Show human-readable file sizes when downloading - see [#363](https://github.com/chocolatey/choco/issues/363) + * API - Add the ability to retrieve package count for a Source - see [#431](https://github.com/chocolatey/choco/issues/431) + * API - Chocolatey Lib still marks vital package information as internal - see [#433](https://github.com/chocolatey/choco/issues/433) + * API - Add paging to list command - see [#427](https://github.com/chocolatey/choco/issues/427) https://github.com/chocolatey/choco diff --git a/nuget/chocolatey/tools/chocolateysetup.psm1 b/nuget/chocolatey/tools/chocolateysetup.psm1 index 1237b28d72..b6f3c37de8 100644 --- a/nuget/chocolatey/tools/chocolateysetup.psm1 +++ b/nuget/chocolatey/tools/chocolateysetup.psm1 @@ -471,7 +471,7 @@ param( $netFx4InstallTries += 1 if (!(Test-Path $NetFx4Installer)) { - Write-Output "Downloading `'$NetFx4Url`' to `'$NetFx4Installer`' - the installer is 40+ MBs, so this could take awhile on a slow connection." + Write-Output "Downloading `'$NetFx4Url`' to `'$NetFx4Installer`' - the installer is 40+ MBs, so this could take a while on a slow connection." (New-Object Net.WebClient).DownloadFile("$NetFx4Url","$NetFx4Installer") } diff --git a/src/chocolatey.console/Program.cs b/src/chocolatey.console/Program.cs index d5b14fe9e8..ba9a53ce25 100644 --- a/src/chocolatey.console/Program.cs +++ b/src/chocolatey.console/Program.cs @@ -178,8 +178,6 @@ private static void report_version_and_exit_if_requested(string[] args, Chocolat } } - static EventHandler _handler; - private static void trap_exit_scenarios(ChocolateyConfiguration config) { ExitScenarioHandler.SetHandler(); @@ -189,7 +187,7 @@ private static void remove_old_chocolatey_exe(IFileSystem fileSystem) { try { - fileSystem.delete_file(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty) + ".old"); + fileSystem.delete_file(fileSystem.get_current_assembly_path() + ".old"); fileSystem.delete_file(fileSystem.combine_paths(AppDomain.CurrentDomain.BaseDirectory, "choco.exe.old")); } catch (Exception ex) diff --git a/src/chocolatey.resources/chocolatey.resources.csproj b/src/chocolatey.resources/chocolatey.resources.csproj index bbd951ecd9..306a783fae 100644 --- a/src/chocolatey.resources/chocolatey.resources.csproj +++ b/src/chocolatey.resources/chocolatey.resources.csproj @@ -43,6 +43,7 @@ + diff --git a/src/chocolatey.resources/helpers/functions/Format-FileSize.ps1 b/src/chocolatey.resources/helpers/functions/Format-FileSize.ps1 new file mode 100644 index 0000000000..c1be440737 --- /dev/null +++ b/src/chocolatey.resources/helpers/functions/Format-FileSize.ps1 @@ -0,0 +1,25 @@ +# Copyright © 2011 - Present 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. + +Function Format-FileSize() { + Param ([double]$size) + Foreach ($unit in @('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB')) { + If ($size -lt 1024) { + return [string]::Format("{0:0.##} {1}", $size, $unit) + } + $size /= 1024 + } + return [string]::Format("{0:0.##} YB", $size) +} \ No newline at end of file diff --git a/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 b/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 index 3affa6fadd..bb71f6d2b2 100644 --- a/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 +++ b/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 @@ -1,15 +1,15 @@ -# Copyright 2011 - Present RealDimensions Software, LLC & original authors/contributors from https://github.com/chocolatey/chocolatey -# -# 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 +# Copyright 2011 - Present RealDimensions Software, LLC & original authors/contributors from https://github.com/chocolatey/chocolatey +# +# 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. function Get-ChocolateyWebFile { @@ -46,6 +46,24 @@ OPTIONAL (Right now) - 'md5', 'sha1', 'sha256' or 'sha512' - defaults to 'md5' .PARAMETER ChecksumType64 OPTIONAL (Right now) - 'md5', 'sha1', 'sha256' or 'sha512' - defaults to ChecksumType +.PARAMETER options +OPTIONAL - Specify custom headers + +Example: +-------- + $options = + @{ + Headers = @{ + Accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; + 'Accept-Charset' = 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'; + 'Accept-Language' = 'en-GB,en-US;q=0.8,en;q=0.6'; + Cookie = 'products.download.email=ewilde@gmail.com'; + Referer = 'http://submain.com/download/ghostdoc/'; + } + } + + Get-ChocolateyWebFile 'ghostdoc' 'http://submain.com/download/GhostDoc_v4.0.zip' -options $options + .EXAMPLE Get-ChocolateyWebFile '__NAME__' 'C:\somepath\somename.exe' 'URL' '64BIT_URL_DELETE_IF_NO_64BIT' @@ -64,7 +82,8 @@ param( [string] $checksum = '', [string] $checksumType = '', [string] $checksum64 = '', - [string] $checksumType64 = $checksumType + [string] $checksumType64 = $checksumType, + [hashtable] $options = @{Headers=@{}} ) Write-Debug "Running 'Get-ChocolateyWebFile' for $packageName with url:`'$url`', fileFullPath:`'$fileFullPath`', url64bit:`'$url64bit`', checksum: `'$checksum`', checksumType: `'$checksumType`', checksum64: `'$checksum64`', checksumType64: `'$checksumType64`'"; @@ -92,9 +111,9 @@ param( } try { - $fileDirectory = $([System.IO.Path]::GetDirectoryName($fileFullPath)) - if (!(Test-Path($fileDirectory))) { - [System.IO.Directory]::CreateDirectory($fileDirectory) | Out-Null + $fileDirectory = $([System.IO.Path]::GetDirectoryName($fileFullPath)) + if (!(Test-Path($fileDirectory))) { + [System.IO.Directory]::CreateDirectory($fileDirectory) | Out-Null } } catch { Write-Host "Attempt to create directory failed for '$fileFullPath'." @@ -138,7 +157,7 @@ param( if ($needsDownload) { Write-Host "Downloading $packageName $bitPackage bit from `'$url`'" - Get-WebFile $url $fileFullPath + Get-WebFile $url $fileFullPath -options $options } } elseif ($url.StartsWith('ftp')) { Write-Host "Ftp-ing $packageName diff --git a/src/chocolatey.resources/helpers/functions/Get-FtpFile.ps1 b/src/chocolatey.resources/helpers/functions/Get-FtpFile.ps1 index 9f7973b353..56e962c0f2 100644 --- a/src/chocolatey.resources/helpers/functions/Get-FtpFile.ps1 +++ b/src/chocolatey.resources/helpers/functions/Get-FtpFile.ps1 @@ -27,6 +27,7 @@ param( # send the ftp request to the server $ftpresponse = $ftprequest.GetResponse() [int]$goal = $ftpresponse.ContentLength + $goalFormatted = Format-FileSize $goal # get a download stream from the server response $reader = $ftpresponse.GetResponseStream() @@ -42,8 +43,9 @@ param( $writer.Write($buffer, 0, $count); if(!$quiet) { $total += $count + $totalFormatted = Format-FileSize $total if($goal -gt 0) { - Write-Progress "Downloading $url to $fileName" "Saving $total of $goal" -id 0 -percentComplete (($total/$goal)*100) + Write-Progress "Downloading $url to $fileName" "Saving $totalFormatted of $goalFormatted ($total/$goal)" -id 0 -percentComplete (($total/$goal)*100) } else { Write-Progress "Downloading $url to $fileName" "Saving $total bytes..." -id 0 -Completed } diff --git a/src/chocolatey.resources/helpers/functions/Get-WebFile.ps1 b/src/chocolatey.resources/helpers/functions/Get-WebFile.ps1 index 4fa387b73b..142edd6009 100644 --- a/src/chocolatey.resources/helpers/functions/Get-WebFile.ps1 +++ b/src/chocolatey.resources/helpers/functions/Get-WebFile.ps1 @@ -20,7 +20,8 @@ param( $fileName = $null, $userAgent = 'chocolatey command line', [switch]$Passthru, - [switch]$quiet + [switch]$quiet, + [hashtable] $options = @{Headers=@{}} ) Write-Debug "Running 'Get-WebFile' for $fileName with url:`'$url`', userAgent: `'$userAgent`' "; #if ($url -eq '' return) @@ -78,6 +79,21 @@ param( $req.UserAgent = $userAgent } + if ($options.Headers.Count -gt 0) { + Write-Debug "Setting custom headers" + foreach ($item in $options.Headers.GetEnumerator()) { + $uri = (new-object system.uri $url) + Write-Debug($item.Key + ':' + $item.Value) + switch ($item.Key) { + 'Accept' {$req.Accept = $item.Value} + 'Cookie' {$req.CookieContainer.SetCookies($uri, $item.Value)} + 'Referer' {$req.Referer = $item.Value} + 'User-Agent' {$req.UserAgent = $item.Value} + Default {$req.Headers.Add($item.Key, $item.Value)} + } + } + } + $res = $req.GetResponse(); if($fileName -and !(Split-Path $fileName)) { @@ -107,6 +123,7 @@ param( if($res.StatusCode -eq 200) { [long]$goal = $res.ContentLength + $goalFormatted = Format-FileSize $goal $reader = $res.GetResponseStream() if ($fileName) { @@ -139,8 +156,9 @@ param( $output += $encoding.GetString($buffer,0,$count) } elseif(!$quiet) { $total += $count + $totalFormatted = Format-FileSize $total if($goal -gt 0 -and ++$iterLoop%10 -eq 0) { - Write-Progress "Downloading $url to $fileName" "Saving $total of $goal" -id 0 -percentComplete (($total/$goal)*100) + Write-Progress "Downloading $url to $fileName" "Saving $totalFormatted of $goalFormatted ($total/$goal)" -id 0 -percentComplete (($total/$goal)*100) } if ($total -eq $goal) { diff --git a/src/chocolatey.resources/helpers/functions/Install-ChocolateyPackage.ps1 b/src/chocolatey.resources/helpers/functions/Install-ChocolateyPackage.ps1 index 057685a596..cb022fb12b 100644 --- a/src/chocolatey.resources/helpers/functions/Install-ChocolateyPackage.ps1 +++ b/src/chocolatey.resources/helpers/functions/Install-ChocolateyPackage.ps1 @@ -53,6 +53,23 @@ OPTIONAL (Right now) - 'md5', 'sha1', 'sha256' or 'sha512' - defaults to 'md5' .PARAMETER ChecksumType64 OPTIONAL (Right now) - 'md5', 'sha1', 'sha256' or 'sha512' - defaults to ChecksumType +.PARAMETER options +OPTIONAL - Specify custom headers + +Example: +-------- + $options = + @{ + Headers = @{ + Accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; + 'Accept-Charset' = 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'; + 'Accept-Language' = 'en-GB,en-US;q=0.8,en;q=0.6'; + Cookie = 'products.download.email=ewilde@gmail.com'; + Referer = 'http://submain.com/download/ghostdoc/'; + } + } + + Get-ChocolateyWebFile 'ghostdoc' 'http://submain.com/download/GhostDoc_v4.0.zip' -options $options .EXAMPLE Install-ChocolateyPackage '__NAME__' 'EXE_OR_MSI' 'SILENT_ARGS' 'URL' '64BIT_URL_DELETE_IF_NO_64BIT' @@ -78,7 +95,8 @@ param( [string] $checksum = '', [string] $checksumType = '', [string] $checksum64 = '', - [string] $checksumType64 = '' + [string] $checksumType64 = '', + [hashtable] $options = @{Headers=@{}} ) Write-Debug "Running 'Install-ChocolateyPackage' for $packageName with url:`'$url`', args: `'$silentArgs`', fileType: `'$fileType`', url64bit: `'$url64bit`', checksum: `'$checksum`', checksumType: `'$checksumType`', checksum64: `'$checksum64`', checksumType64: `'$checksumType64`', validExitCodes: `'$validExitCodes`' "; @@ -90,6 +108,6 @@ param( if (![System.IO.Directory]::Exists($tempDir)) { [System.IO.Directory]::CreateDirectory($tempDir) | Out-Null } $file = Join-Path $tempDir "$($packageName)Install.$fileType" - Get-ChocolateyWebFile $packageName $file $url $url64bit -checksum $checksum -checksumType $checksumType -checksum64 $checksum64 -checksumType64 $checksumType64 + Get-ChocolateyWebFile $packageName $file $url $url64bit -checksum $checksum -checksumType $checksumType -checksum64 $checksum64 -checksumType64 $checksumType64 -options $options Install-ChocolateyInstallPackage $packageName $fileType $silentArgs $file -validExitCodes $validExitCodes } diff --git a/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 b/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 index fe39f08d79..43fb3c8cc7 100644 --- a/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 +++ b/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 @@ -45,6 +45,24 @@ OPTIONAL (Right now) - 'md5', 'sha1', 'sha256' or 'sha512' - defaults to 'md5' .PARAMETER ChecksumType64 OPTIONAL (Right now) - 'md5', 'sha1', 'sha256' or 'sha512' - defaults to ChecksumType +.PARAMETER options +OPTIONAL - Specify custom headers + +Example: +-------- + $options = + @{ + Headers = @{ + Accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; + 'Accept-Charset' = 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'; + 'Accept-Language' = 'en-GB,en-US;q=0.8,en;q=0.6'; + Cookie = 'products.download.email=ewilde@gmail.com'; + Referer = 'http://submain.com/download/ghostdoc/'; + } + } + + Get-ChocolateyWebFile 'ghostdoc' 'http://submain.com/download/GhostDoc_v4.0.zip' -options $options + .EXAMPLE Install-ChocolateyZipPackage '__NAME__' 'URL' "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" @@ -68,7 +86,8 @@ param( [string] $checksum = '', [string] $checksumType = '', [string] $checksum64 = '', - [string] $checksumType64 = '' + [string] $checksumType64 = '', + [hashtable] $options = @{Headers=@{}} ) Write-Debug "Running 'Install-ChocolateyZipPackage' for $packageName with url:`'$url`', unzipLocation: `'$unzipLocation`', url64bit: `'$url64bit`', specificFolder: `'$specificFolder`', checksum: `'$checksum`', checksumType: `'$checksumType`', checksum64: `'$checksum64`', checksumType64: `'$checksumType64`' "; @@ -81,6 +100,6 @@ param( if (![System.IO.Directory]::Exists($tempDir)) {[System.IO.Directory]::CreateDirectory($tempDir) | Out-Null} $file = Join-Path $tempDir "$($packageName)Install.$fileType" - Get-ChocolateyWebFile $packageName $file $url $url64bit -checkSum $checkSum -checksumType $checksumType -checkSum64 $checkSum64 -checksumType64 $checksumType64 + Get-ChocolateyWebFile $packageName $file $url $url64bit -checkSum $checkSum -checksumType $checksumType -checkSum64 $checkSum64 -checksumType64 $checksumType64 -options $options Get-ChocolateyUnzip "$file" $unzipLocation $specificFolder $packageName } diff --git a/src/chocolatey.tests.integration/NUnitSetup.cs b/src/chocolatey.tests.integration/NUnitSetup.cs index 4b3aa1a965..f887003ed0 100644 --- a/src/chocolatey.tests.integration/NUnitSetup.cs +++ b/src/chocolatey.tests.integration/NUnitSetup.cs @@ -57,7 +57,7 @@ private static void fix_application_parameter_variables(Container container) { var fileSystem = container.GetInstance(); - var applicationLocation = fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + var applicationLocation = fileSystem.get_directory_name(fileSystem.get_current_assembly_path()); var field = typeof (ApplicationParameters).GetField("InstallLocation"); field.SetValue(null, applicationLocation); diff --git a/src/chocolatey.tests.integration/Scenario.cs b/src/chocolatey.tests.integration/Scenario.cs index a9631037ce..b85b43d880 100644 --- a/src/chocolatey.tests.integration/Scenario.cs +++ b/src/chocolatey.tests.integration/Scenario.cs @@ -36,7 +36,7 @@ public class Scenario public static string get_top_level() { - return _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + return _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); } public static string get_package_install_path() diff --git a/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs b/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs index 07d67cb769..0b40494b80 100644 --- a/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs @@ -46,8 +46,8 @@ public override void Context() } public override void Because() - { - result = CommandExecutor.execute("cmd.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, fileSystem.get_current_directory(), null, (s, e) => { errorOutput += e.Data; }, updateProcessPath: false, allowUseWindow: false); + { + result = commandExecutor.execute("cmd.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, fileSystem.get_current_directory(), null, (s, e) => { errorOutput += e.Data; }, updateProcessPath: false, allowUseWindow: false); } [Fact] diff --git a/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs index 20aa0947e7..60f822664a 100644 --- a/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs @@ -35,7 +35,7 @@ public override void Context() { FileSystem = new DotNetFileSystem(); Provider = new CrytpoHashProvider(FileSystem,CryptoHashProviderType.Md5); - ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), "context"); + ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(FileSystem.get_current_assembly_path()), "context"); } } diff --git a/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs b/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs index 750d8c1bef..f49d9e5cd2 100644 --- a/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs @@ -21,6 +21,7 @@ namespace chocolatey.tests.integration.infrastructure.filesystem using NUnit.Framework; using Should; using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.platforms; public class DotNetFileSystemSpecs { @@ -48,6 +49,53 @@ public override void Context() } } + [Category("Integration")] + public class when_finding_paths_to_executables_with_dotNetFileSystem : DotNetFileSystemSpecsBase + { + public override void Because() + { + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable() + { + FileSystem.get_executable_path("cmd").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd" + ); + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable_with_extension() + { + FileSystem.get_executable_path("cmd.exe").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd" + ); + } + + [Fact] + public void GetExecutablePath_should_return_same_value_when_executable_is_not_found() + { + FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea"); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_null() + { + FileSystem.get_executable_path(null).ShouldEqual(string.Empty); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string() + { + FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty); + } + + } + [Category("Integration")] public class when_doing_file_system_operations_with_dotNetFileSystem : DotNetFileSystemSpecsBase { diff --git a/src/chocolatey.tests/TinySpec.cs b/src/chocolatey.tests/TinySpec.cs index 71b049464b..c2ce986c81 100644 --- a/src/chocolatey.tests/TinySpec.cs +++ b/src/chocolatey.tests/TinySpec.cs @@ -145,6 +145,19 @@ public PendingAttribute(string reason) } } +#if __MonoCS__ + public class WindowsOnlyAttribute : IgnoreAttribute + { + public WindowsOnlyAttribute() : base("This is a Windows only test") + { + } + } +#else + public class WindowsOnlyAttribute : Attribute + { + } +#endif + public class IntegrationAttribute : CategoryAttribute { public IntegrationAttribute() diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index d859a51aef..ad8b4b20f5 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -82,6 +82,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateySourceCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateySourceCommandSpecs.cs index aecaf9d987..55e6654e34 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateySourceCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateySourceCommandSpecs.cs @@ -355,7 +355,7 @@ public void should_call_service_source_add_when_command_is_add() because(); configSettingsService.Verify(c => c.source_add(configuration), Times.Once); } - + [Fact] public void should_call_service_source_remove_when_command_is_remove() { @@ -380,5 +380,23 @@ public void should_call_service_source_enable_when_command_is_enable() configSettingsService.Verify(c => c.source_enable(configuration), Times.Once); } } + + public class when_list_is_called : ChocolateySourceCommandSpecsBase + { + private Action because; + + public override void Because() + { + because = () => command.list(configuration); + } + + [Fact] + public void should_call_service_source_list_when_command_is_list() + { + configuration.SourceCommand.Command = SourceCommandType.list; + because(); + configSettingsService.Verify(c => c.source_list(configuration), Times.Once); + } + } } } \ No newline at end of file diff --git a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs index 6cfaad16a4..1cebc0d7ef 100644 --- a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs @@ -77,7 +77,7 @@ public override void Context() packageResults.GetOrAdd("regular", packageResult); fileSystem.Setup(f => f.directory_exists(registryKeys.FirstOrDefault().InstallLocation)).Returns(true); - registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(true); + registryService.Setup(r => r.installer_value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(true); fileSystem.Setup(f => f.get_full_path(expectedUninstallString)).Returns(expectedUninstallString); } } @@ -235,7 +235,7 @@ public override void Context() { base.Context(); registryService.ResetCalls(); - registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); + registryService.Setup(r => r.installer_value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); } public override void Because() @@ -264,7 +264,7 @@ public override void Context() fileSystem.ResetCalls(); fileSystem.Setup(f => f.directory_exists(registryKeys.FirstOrDefault().InstallLocation)).Returns(false); registryService.ResetCalls(); - registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); + registryService.Setup(r => r.installer_value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); } public override void Because() diff --git a/src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs new file mode 100644 index 0000000000..f2ec5feca2 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs @@ -0,0 +1,147 @@ +// Copyright © 2011 - Present 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.tests.infrastructure.app.services +{ + using System; + using Microsoft.Win32; + using Moq; + using Should; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.services; + using Registry = chocolatey.infrastructure.app.domain.Registry; + + public class RegistryServiceSpecs + { + public abstract class RegistryServiceSpecsBase : TinySpec + { + protected RegistryService Service; + protected Mock FileSystem = new Mock(); + protected Mock XmlService = new Mock(); + + public override void Context() + { + reset(); + Service = new RegistryService(XmlService.Object, FileSystem.Object); + } + + protected void reset() + { + FileSystem.ResetCalls(); + XmlService.ResetCalls(); + MockLogger.reset(); + } + } + + [WindowsOnly] + public class when_RegistryService_get_installer_keys_is_called : RegistryServiceSpecsBase + { + private Registry _result; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + _result = Service.get_installer_keys(); + } + + + [Fact] + public void should_not_be_null() + { + _result.ShouldNotBeNull(); + } + + } + + [WindowsOnly] + public class when_RegistryService_get_key_is_called_for_a_value_that_exists : RegistryServiceSpecsBase + { + private RegistryKey _result; + private RegistryHive _hive = RegistryHive.CurrentUser; + private string _subkeyPath = "Console"; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + _result = Service.get_key(_hive, _subkeyPath); + } + + [Fact] + public void should_return_a_non_null_value() + { + _result.ShouldNotBeNull(); + } + + [Fact] + public void should_return_a_value_of_type_RegistryKey() + { + _result.ShouldBeType(); + } + + [Fact] + public void should_contain_keys() + { + _result.GetSubKeyNames().ShouldNotBeEmpty(); + } + + [Fact] + public void should_contain_values() + { + _result.GetValueNames().ShouldNotBeEmpty(); + } + } + + [WindowsOnly] + public class when_RegistryService_get_key_is_called_for_a_value_that_does_not_exist : RegistryServiceSpecsBase + { + private RegistryKey _result; + private RegistryHive _hive = RegistryHive.CurrentUser; + private string _subkeyPath = "Software\\alsdjfalskjfaasdfasdf"; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + _result = Service.get_key(_hive, _subkeyPath); + } + + [Fact] + public void should_not_error() + { + //nothing to see here + } + + [Fact] + public void should_return_null_key() + { + _result.ShouldBeNull(); + } + } + + //todo a subkey that exists only in 32 bit mode (must create it to test it though) + } +} \ No newline at end of file diff --git a/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs b/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs index f7ca43dcbc..304eeec1ea 100644 --- a/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs +++ b/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs @@ -18,7 +18,10 @@ namespace chocolatey.tests.infrastructure.filesystem using System; using System.IO; using NUnit.Framework; + using Moq; using Should; + using chocolatey.infrastructure.adapters; + using chocolatey.infrastructure.app; using chocolatey.infrastructure.filesystem; using chocolatey.infrastructure.platforms; @@ -74,8 +77,8 @@ public void GetExtension_should_return_the_extension_of_the_filename_even_with_a public void GetDirectoryName_should_return_the_directory_of_the_path_to_the_file() { FileSystem.get_directory_name("C:\\temp\\test.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp" : "C:/temp"); } @@ -83,8 +86,8 @@ public void GetDirectoryName_should_return_the_directory_of_the_path_to_the_file public void Combine_should_combine_the_file_paths_of_all_the_included_items_together() { FileSystem.combine_paths("C:\\temp", "yo", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\filename.txt" : "C:/temp/yo/filename.txt"); } @@ -92,8 +95,8 @@ public void Combine_should_combine_the_file_paths_of_all_the_included_items_toge public void Combine_should_combine_when_paths_have_backslashes_in_subpaths() { FileSystem.combine_paths("C:\\temp", "yo\\timmy", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\timmy\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\timmy\\filename.txt" : "C:/temp/yo/timmy/filename.txt"); } @@ -101,17 +104,17 @@ public void Combine_should_combine_when_paths_have_backslashes_in_subpaths() public void Combine_should_combine_when_paths_start_with_backslashes_in_subpaths() { FileSystem.combine_paths("C:\\temp", "\\yo", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\filename.txt" : "C:/temp/yo/filename.txt"); } - + [Fact] public void Combine_should_combine_when_paths_start_with_forwardslashes_in_subpaths() { FileSystem.combine_paths("C:\\temp", "/yo", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\filename.txt" : "C:/temp/yo/filename.txt"); } @@ -122,5 +125,115 @@ public void Combine_should_error_if_any_path_but_the_primary_contains_colon() FileSystem.combine_paths("C:\\temp", "C:"); } } + + public class when_finding_paths_to_executables_with_dotNetFileSystem : DotNetFileSystemSpecsBase + { + public Mock _environment = new Mock(); + + public override void Context() + { + base.Context(); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions)).Returns(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL"); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.Path)).Returns( + @"C:\ProgramData\Chocolatey\bin{0}C:\Program Files\Microsoft\Web Platform Installer\{0}C:\Users\yes\AppData\Roaming\Boxstarter{0}C:\tools\ChocolateyPackageUpdater{0}C:\Windows\system32{0}C:\Windows{0}C:\Windows\System32\Wbem{0}C:\Windows\System32\WindowsPowerShell\v1.0\{0}" + .format_with(Path.PathSeparator) + ); + FileSystem.initialize_with(new Lazy(() => _environment.Object)); + } + + public override void Because() + { + } + + private void reset() + { + _environment.ResetCalls(); + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable() + { + FileSystem.get_executable_path("cmd").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd" + ); + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable_with_extension() + { + FileSystem.get_executable_path("cmd.exe").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd.exe" + ); + } + + [Fact] + public void GetExecutablePath_should_return_same_value_when_executable_is_not_found() + { + FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea"); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_null() + { + FileSystem.get_executable_path(null).ShouldEqual(string.Empty); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string() + { + FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty); + } + } + + public class when_finding_paths_to_executables_with_dotNetFileSystem_with_empty_path_extensions : DotNetFileSystemSpecsBase + { + public Mock _environment = new Mock(); + + public override void Context() + { + base.Context(); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions)).Returns(string.Empty); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.Path)).Returns( + "/usr/local/bin{0}/usr/bin/{0}/bin{0}/usr/sbin{0}/sbin" + .format_with(Path.PathSeparator) + ); + FileSystem.initialize_with(new Lazy(() => _environment.Object)); + } + + public override void Because() + { + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable() + { + FileSystem.get_executable_path("ls").ShouldEqual( + Platform.get_platform() != PlatformType.Windows ? + "/bin/ls" + : "ls"); + } + + [Fact] + public void GetExecutablePath_should_return_same_value_when_executable_is_not_found() + { + FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea"); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_null() + { + FileSystem.get_executable_path(null).ShouldEqual(string.Empty); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string() + { + FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty); + } + } } } \ No newline at end of file diff --git a/src/chocolatey/GetChocolatey.cs b/src/chocolatey/GetChocolatey.cs index e6487625e5..d35529b113 100644 --- a/src/chocolatey/GetChocolatey.cs +++ b/src/chocolatey/GetChocolatey.cs @@ -185,6 +185,36 @@ public void RunConsole(string[] args) runner.run(args, configuration, _container); } + /// + /// Run chocolatey after setting the options, and list the results. + /// + /// The typer of results you're expecting back. + public IEnumerable List() + { + extract_resources(); + var configuration = create_configuration(new List()); + configuration.RegularOutput = true; + var runner = new GenericRunner(); + return runner.list(configuration, _container, isConsole: false, parseArgs: null); + } + + /// + /// Run chocolatey after setting the options, + /// and get the count of items that would be returned if you listed the results. + /// + /// + /// Is intended to be more efficient then simply calling List and then Count() on the returned list. + /// It also returns the full count as is ignores paging. + /// + public int ListCount() + { + extract_resources(); + var configuration = create_configuration(new List()); + configuration.RegularOutput = true; + var runner = new GenericRunner(); + return runner.count(configuration, _container, isConsole: false, parseArgs: null); + } + private ChocolateyConfiguration create_configuration(IList args) { var configuration = new ChocolateyConfiguration(); @@ -216,4 +246,4 @@ private void extract_resources() } // ReSharper restore InconsistentNaming -} \ No newline at end of file +} diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index c7d2711451..0cf1733747 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -92,6 +92,7 @@ + @@ -110,10 +111,15 @@ + + + + + @@ -188,7 +194,7 @@ - + @@ -282,4 +288,4 @@ --> - \ No newline at end of file + diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index ecfd78fd18..755e6c2d1f 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -30,12 +30,12 @@ public static class ApplicationParameters public static readonly string ChocolateyInstallEnvironmentVariableName = "ChocolateyInstall"; public static readonly string Name = "Chocolatey"; #if DEBUG - public static readonly string InstallLocation = _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + public static readonly string InstallLocation = _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); #else - public static readonly string InstallLocation = Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + public static readonly string InstallLocation = System.Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); #endif - public static readonly string CommonAppDataChocolatey = _fileSystem.combine_paths(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Name); + public static readonly string CommonAppDataChocolatey = _fileSystem.combine_paths(System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData), Name); public static readonly string LoggingLocation = _fileSystem.combine_paths(InstallLocation, "logs"); public static readonly string LoggingFile = @"chocolatey.log"; public static readonly string Log4NetConfigurationAssembly = @"chocolatey"; @@ -54,10 +54,18 @@ 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 ChocolateyCommunityFeedPushSource = "https://chocolatey.org/"; + public static readonly string ChocolateyCommunityFeedSource = "https://chocolatey.org/api/v2/"; public static readonly string UserAgent = "Chocolatey Command Line"; public static readonly string RegistryValueInstallLocation = "InstallLocation"; public static readonly string AllPackages = "all"; + public static class Environment + { + public static readonly string Path = "Path"; + public static readonly string PathExtensions = "PATHEXT"; + public static readonly string PathExtensionsSeparator = ";"; + } + /// /// Default is 45 minutes /// @@ -100,22 +108,6 @@ public static class Messages public static readonly string NugetEventActionHeader = "Nuget called an event"; } - public static class OutputParser - { - //todo: This becomes the WebPI parsing stuff instead - public static class Nuget - { - public const string PACKAGE_NAME_GROUP = "PkgName"; - public const string PACKAGE_VERSION_GROUP = "PkgVersion"; - public static readonly Regex AlreadyInstalled = new Regex(@"already installed", RegexOptions.Compiled); - public static readonly Regex NotInstalled = new Regex(@"not installed", RegexOptions.Compiled); - public static readonly Regex Installing = new Regex(@"Installing", RegexOptions.Compiled); - public static readonly Regex ResolvingDependency = new Regex(@"Attempting to resolve dependency", RegexOptions.Compiled); - public static readonly Regex PackageName = new Regex(@"'(?<{0}>[.\S]+)\s?".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); - public static readonly Regex PackageVersion = new Regex(@"(?<{0}>[\d\.]+[\-\w]*)[[)]?'".format_with(PACKAGE_VERSION_GROUP), RegexOptions.Compiled); - } - } - private static T try_get_config(Func func, T defaultValue) { try diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index af1b7934c9..0478b1552e 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -1,12 +1,12 @@ // Copyright © 2011 - Present 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. @@ -93,7 +93,7 @@ private static void set_file_configuration(ChocolateyConfiguration config, IFile } set_machine_sources(config, configFileSettings); - + set_config_items(config, configFileSettings, fileSystem); FaultTolerance.try_catch_with_logging_exception( @@ -138,12 +138,12 @@ private static void set_config_items(ChocolateyConfiguration config, ConfigFileS var commandExecutionTimeoutSeconds = -1; int.TryParse( set_config_item( - ApplicationParameters.ConfigSettings.CommandExecutionTimeoutSeconds, - configFileSettings, - originalCommandTimeout == 0 ? - ApplicationParameters.DefaultWaitForExitInSeconds.to_string() - : originalCommandTimeout.to_string(), - "Default timeout for command execution."), + ApplicationParameters.ConfigSettings.CommandExecutionTimeoutSeconds, + configFileSettings, + originalCommandTimeout == 0 ? + ApplicationParameters.DefaultWaitForExitInSeconds.to_string() + : originalCommandTimeout.to_string(), + "Default timeout for command execution."), out commandExecutionTimeoutSeconds); config.CommandExecutionTimeoutSeconds = commandExecutionTimeoutSeconds; if (configFileSettings.CommandExecutionTimeoutSeconds <= 0) @@ -184,33 +184,38 @@ private static string set_config_item(string configName, ConfigFileSettings conf private static void set_feature_flags(ChocolateyConfiguration config, ConfigFileSettings configFileSettings) { - config.Features.CheckSumFiles = set_feature_flag(ApplicationParameters.Features.CheckSumFiles, configFileSettings, "Checksum files when pulled in from internet (based on package)."); - config.Features.AutoUninstaller = set_feature_flag(ApplicationParameters.Features.AutoUninstaller, configFileSettings, "Uninstall from programs and features without requiring an explicit uninstall script."); - config.Features.FailOnAutoUninstaller = set_feature_flag(ApplicationParameters.Features.FailOnAutoUninstaller, configFileSettings, "Fail if automatic uninstaller fails."); - config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, "Prompt for confirmation in scripts or bypass."); + config.Features.CheckSumFiles = set_feature_flag(ApplicationParameters.Features.CheckSumFiles, configFileSettings, defaultEnabled: true, description: "Checksum files when pulled in from internet (based on package)."); + config.Features.AutoUninstaller = set_feature_flag(ApplicationParameters.Features.AutoUninstaller, configFileSettings, defaultEnabled: true, description: "Uninstall from programs and features without requiring an explicit uninstall script."); + config.Features.FailOnAutoUninstaller = set_feature_flag(ApplicationParameters.Features.FailOnAutoUninstaller, configFileSettings, defaultEnabled: false, description: "Fail if automatic uninstaller fails."); + config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass."); } - private static bool set_feature_flag(string featureName, ConfigFileSettings configFileSettings, string description) + private static bool set_feature_flag(string featureName, ConfigFileSettings configFileSettings, bool defaultEnabled, string description) { - var enabled = false; var feature = configFileSettings.Features.FirstOrDefault(f => f.Name.is_equal_to(featureName)); - if (feature != null && feature.Enabled) enabled = true; if (feature == null) { feature = new ConfigFileFeatureSetting { Name = featureName, - Enabled = enabled, + Enabled = defaultEnabled, Description = description }; configFileSettings.Features.Add(feature); } - + else + { + if (!feature.SetExplicitly && feature.Enabled != defaultEnabled) + { + feature.Enabled = defaultEnabled; + } + } + feature.Description = description; - - return enabled; + + return feature != null ? feature.Enabled : defaultEnabled; } private static void set_global_options(IList args, ChocolateyConfiguration config) @@ -280,7 +285,7 @@ private static void set_global_options(IList args, ChocolateyConfigurati "chocolatey".Log().Info(@" {0} -Please run chocolatey with `choco command -help` for specific help on +Please run chocolatey with `choco command -help` for specific help on each command. ".format_with(commandsLog.ToString())); "chocolatey".Log().Info(ChocolateyLoggers.Important, @"How To Pass Options / Switches"); @@ -289,24 +294,24 @@ each command. * `-`, `/`, or `--` (one character switches should not use `--`) * **Option Bundling / Bundled Options**: One character switches can be - bundled. e.g. `-d` (debug), `-f` (force), `-v` (verbose), and `-y` + bundled. e.g. `-d` (debug), `-f` (force), `-v` (verbose), and `-y` (confirm yes) can be bundled as `-dfvy`. - * ***Note:*** If `debug` or `verbose` are bundled with local options + * ***Note:*** If `debug` or `verbose` are bundled with local options (not the global ones above), some logging may not show up until after the local options are parsed. - * **Use Equals**: You can also include or not include an equals sign + * **Use Equals**: You can also include or not include an equals sign `=` between options and values. - * **Quote Values**: When you need to quote things, such as when using - spaces, please use apostrophes (`'value'`). In cmd.exe you may be - able to use just double quotes (`""value""`) but in powershell.exe - you may need to either escape the quotes with backticks - (`` `""value`"" ``) or use a combination of double quotes and - apostrophes (`""'value'""`). This is due to the hand off to + * **Quote Values**: When you need to quote things, such as when using + spaces, please use apostrophes (`'value'`). In cmd.exe you may be + able to use just double quotes (`""value""`) but in powershell.exe + you may need to either escape the quotes with backticks + (`` `""value`"" ``) or use a combination of double quotes and + apostrophes (`""'value'""`). This is due to the hand off to PowerShell - it seems to strip off the outer set of quotes. - * Options and switches apply to all items passed, so if you are - installing multiple packages, and you use `--version=1.0.0`, choco - is going to look for and try to install version 1.0.0 of every - package passed. So please split out multiple package calls when + * Options and switches apply to all items passed, so if you are + installing multiple packages, and you use `--version=1.0.0`, choco + is going to look for and try to install version 1.0.0 of every + package passed. So please split out multiple package calls when wanting to pass specific options. "); "chocolatey".Log().Info(ChocolateyLoggers.Important, "Default Options and Switches"); @@ -372,4 +377,4 @@ public static void set_environment_variables(ChocolateyConfiguration config) } } } -} \ No newline at end of file +} diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index 8c7539a5c0..e63eead688 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -187,6 +187,7 @@ public void noop(ChocolateyConfiguration configuration) public void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.install_run(configuration); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs index 0d805f1b56..32cce2b02b 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs @@ -15,6 +15,7 @@ namespace chocolatey.infrastructure.app.commands { + using System; using System.Collections.Generic; using System.Linq; using attributes; @@ -61,6 +62,22 @@ public void configure_argument_parser(OptionSet optionSet, ChocolateyConfigurati .Add("p=|password=", "Password - the user's password to the source. Defaults to empty.", option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("page=", + "Page - the 'page' of results to return. Defaults to return all results.", option => + { + int page; + if (int.TryParse(option, out page)) + { + configuration.ListCommand.Page = page; + } + else + { + configuration.ListCommand.Page = null; + } + }) + .Add("page-size=", + "Page Size - the amount of package results to return per page. Defaults to 25.", + option => configuration.ListCommand.PageSize = int.Parse(option)) ; //todo exact name } @@ -94,6 +111,7 @@ choco list [] choco list --local-only choco list -li choco list -lai + choco list --page=0 --page-size=25 choco search git choco search git -s ""https://somewhere/out/there"" choco search bob -s ""https://somewhere/protected"" -u user -p pass @@ -110,7 +128,8 @@ public void noop(ChocolateyConfiguration configuration) public void run(ChocolateyConfiguration configuration) { - // you must leave the .ToList() here or else the method won't be evaluated! + _packageService.ensure_source_app_installed(configuration); + // note: you must leave the .ToList() here or else the method won't be evaluated! _packageService.list_run(configuration).ToList(); } @@ -121,6 +140,12 @@ public IEnumerable list(ChocolateyConfiguration configuration) return _packageService.list_run(configuration); } + public int count(ChocolateyConfiguration config) + { + config.QuietOutput = true; + return _packageService.count_run(config); + } + public bool may_require_admin_access() { return false; diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs index 6ccf7fc8ae..c13331b055 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs @@ -28,7 +28,7 @@ namespace chocolatey.infrastructure.app.commands [CommandFor(CommandNameType.sources)] [CommandFor(CommandNameType.source)] - public sealed class ChocolateySourceCommand : ICommand + public sealed class ChocolateySourceCommand : IListCommand { private readonly IChocolateyConfigSettingsService _configSettingsService; @@ -143,9 +143,19 @@ public void run(ChocolateyConfiguration configuration) } } + public IEnumerable list(ChocolateyConfiguration configuration) + { + return _configSettingsService.source_list(configuration); + } + + public int count(ChocolateyConfiguration config) + { + return list(config).Count(); + } + public bool may_require_admin_access() { return true; } } -} \ No newline at end of file +} diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs index e1e4134a57..a9d545efbd 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs @@ -38,6 +38,9 @@ public ChocolateyUninstallCommand(IChocolateyPackageService packageService) public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) { optionSet + .Add("s=|source=", + "Source - The source to find the package(s) to install. Special sources include: ruby, webpi, cygwin, windowsfeatures, and python. Defaults to default feeds.", + option => configuration.Sources = option) .Add("version=", "Version - A specific version to uninstall. Defaults to unspecified.", option => configuration.Version = option.remove_surrounding_quotes()) @@ -128,6 +131,7 @@ public void noop(ChocolateyConfiguration configuration) public void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.uninstall_run(configuration); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs index 49c257fc76..5b7b9e7a23 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs @@ -98,7 +98,7 @@ public void run(ChocolateyConfiguration configuration) AssemblyFileExtractor.extract_all_resources_to_relative_directory( _fileSystem, assembly, - _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), + _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()), folders, ApplicationParameters.ChocolateyFileResources, overwriteExisting: configuration.Force, diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index a44c72e233..edd510f6e5 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -156,6 +156,7 @@ public virtual void noop(ChocolateyConfiguration configuration) public virtual void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.upgrade_run(configuration); } diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index e38c859130..4012f90726 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -33,6 +33,7 @@ public ChocolateyConfiguration() { RegularOutput = true; PromptForConfirmation = true; + SourceType = SourceType.normal; Information = new InformationCommandConfiguration(); Features = new FeaturesConfiguration(); NewCommand = new NewCommandConfiguration(); @@ -145,6 +146,7 @@ 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; } // top level commands @@ -340,9 +342,16 @@ public sealed class FeaturesConfiguration [Serializable] public sealed class ListCommandConfiguration { + public ListCommandConfiguration() + { + PageSize = 25; + } + // list public bool LocalOnly { get; set; } public bool IncludeRegistryPrograms { get; set; } + public int? Page { get; set; } + public int PageSize { get; set; } } [Serializable] diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateySource.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateySource.cs new file mode 100644 index 0000000000..a9eb754a16 --- /dev/null +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateySource.cs @@ -0,0 +1,30 @@ +// Copyright © 2015 - Present 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.configuration +{ + public class ChocolateySource + { + public string Id { get; set; } + + public string Value { get; set; } + + public bool Disabled { get; set; } + + public bool Authenticated { get; set; } + + public int Priority { get; set; } + } +} diff --git a/src/chocolatey/infrastructure.app/domain/SpecialSourceType.cs b/src/chocolatey/infrastructure.app/domain/SourceType.cs similarity index 92% rename from src/chocolatey/infrastructure.app/domain/SpecialSourceType.cs rename to src/chocolatey/infrastructure.app/domain/SourceType.cs index 2ae07f5e69..1b7c156d31 100644 --- a/src/chocolatey/infrastructure.app/domain/SpecialSourceType.cs +++ b/src/chocolatey/infrastructure.app/domain/SourceType.cs @@ -18,13 +18,14 @@ namespace chocolatey.infrastructure.app.domain /// /// Special source modifiers that use alternate sources for packages /// - public enum SpecialSourceType + public enum SourceType { //this is what it should be when it's not set normal, webpi, ruby, python, + windowsfeature, windowsfeatures, cygwin, } diff --git a/src/chocolatey/infrastructure.app/nuget/NugetList.cs b/src/chocolatey/infrastructure.app/nuget/NugetList.cs index 58ab1dc6a8..b3125d0da3 100644 --- a/src/chocolatey/infrastructure.app/nuget/NugetList.cs +++ b/src/chocolatey/infrastructure.app/nuget/NugetList.cs @@ -17,6 +17,7 @@ namespace chocolatey.infrastructure.app.nuget { using System.Collections.Generic; using System.Linq; + using NuGet; using configuration; @@ -25,13 +26,47 @@ namespace chocolatey.infrastructure.app.nuget public static class NugetList { public static IEnumerable GetPackages(ChocolateyConfiguration configuration, ILogger nugetLogger) + { + return execute_package_search(configuration, nugetLogger); + } + + public static int GetCount(ChocolateyConfiguration configuration, ILogger nugetLogger) + { + return execute_package_search(configuration, nugetLogger).Count(); + } + + private static IQueryable execute_package_search(ChocolateyConfiguration configuration, ILogger nugetLogger) { var packageRepository = NugetCommon.GetRemoteRepository(configuration, nugetLogger); + + // Whether or not the package is remote determines two things: + // 1. Does the repository have a notion of "listed"? + // 2. Does it support prerelease in a straight-forward way? + // Choco previously dealt with this by taking the path of least resistance and manually filtering out and sort unwanted packages + // This result in blocking operations that didn't let service based repositories, like OData, take care of heavy lifting on the server. + bool isRemote; + var aggregateRepo = packageRepository as AggregateRepository; + if (aggregateRepo != null) + { + isRemote = aggregateRepo.Repositories.All(repo => repo is IServiceBasedRepository); + } + else + { + isRemote = packageRepository is IServiceBasedRepository; + } + IQueryable results = packageRepository.Search(configuration.Input, configuration.Prerelease); if (configuration.AllVersions) { - return results.Where(PackageExtensions.IsListed).OrderBy(p => p.Id); + if (isRemote) + { + return results.OrderBy(p => p.Id); + } + else + { + return results.Where(PackageExtensions.IsListed).OrderBy(p => p.Id).AsQueryable(); + } } if (configuration.Prerelease && packageRepository.SupportsPrereleasePackages) @@ -43,12 +78,25 @@ public static IEnumerable GetPackages(ChocolateyConfiguration configur results = results.Where(p => p.IsLatestVersion); } - return results.OrderBy(p => p.Id) - .AsEnumerable() + if (!isRemote) + { + results = + results .Where(PackageExtensions.IsListed) .Where(p => configuration.Prerelease || p.IsReleaseVersion()) - .distinct_last(PackageEqualityComparer.Id, PackageComparer.Version); - } + .distinct_last(PackageEqualityComparer.Id, PackageComparer.Version) + .AsQueryable(); + } + + if (configuration.ListCommand.Page.HasValue) + { + results = results.Skip(configuration.ListCommand.PageSize * configuration.ListCommand.Page.Value).Take(configuration.ListCommand.PageSize); + } + + return results.OrderBy(p => p.Id); + } + + } // ReSharper restore InconsistentNaming diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 66e35cf7fa..34ff70eece 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -90,6 +90,20 @@ public void RegisterComponents(Container container) return list.AsReadOnly(); }, Lifestyle.Singleton); + container.Register>(() => + { + 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); diff --git a/src/chocolatey/infrastructure.app/runners/GenericRunner.cs b/src/chocolatey/infrastructure.app/runners/GenericRunner.cs index 1e4436999d..43f78e6150 100644 --- a/src/chocolatey/infrastructure.app/runners/GenericRunner.cs +++ b/src/chocolatey/infrastructure.app/runners/GenericRunner.cs @@ -13,15 +13,18 @@ // See the License for the specific language governing permissions and // limitations under the License. + namespace chocolatey.infrastructure.app.runners { using System; using System.Linq; + using System.Collections.Generic; using SimpleInjector; using adapters; using attributes; using commandline; using configuration; + using domain; using infrastructure.commands; using logging; using Console = System.Console; @@ -29,7 +32,7 @@ namespace chocolatey.infrastructure.app.runners public sealed class GenericRunner { - public void run(ChocolateyConfiguration config, Container container, bool isConsole, Action parseArgs) + private ICommand find_command(ChocolateyConfiguration config, Container container, bool isConsole, Action parseArgs) { var commands = container.GetAllInstances(); var command = commands.Where((c) => @@ -60,6 +63,8 @@ public void run(ChocolateyConfiguration config, Container container, bool isCons parseArgs.Invoke(command); } + set_source_type(config); + this.Log().Debug(() => "Configuration: {0}".format_with(config.ToString())); @@ -89,7 +94,7 @@ Chocolatey is not an official build (bypassed with --allow-unofficial). If you are seeing this message and it is not expected, your system may now be in a bad state. Only official builds are to be trusted. " - ); + ); } } @@ -102,12 +107,64 @@ now be in a bad state. Only official builds are to be trusted. } command.noop(config); + return null; } - else + } + return command; + } + + private void set_source_type(ChocolateyConfiguration config) + { + var sourceType = SourceType.normal; + Enum.TryParse(config.Sources, true, out sourceType); + config.SourceType = sourceType; + + this.Log().Debug(()=> "The source '{0}' evaluated to a '{1}' source type".format_with(config.Sources,sourceType.to_string())); + } + + public void run(ChocolateyConfiguration config, Container container, bool isConsole, Action parseArgs) + { + var command = find_command(config, container, isConsole, parseArgs); + if(command != null) + { + this.Log().Debug("_ {0}:{1} - Normal Run Mode _".format_with(ApplicationParameters.Name, command.GetType().Name)); + command.run(config); + } + } + + public IEnumerable list(ChocolateyConfiguration config, Container container, bool isConsole, Action parseArgs) + { + var command = find_command(config, container, isConsole, parseArgs) as IListCommand; + if (command == null) + { + if (!string.IsNullOrWhiteSpace(config.CommandName)) { - this.Log().Debug("_ {0}:{1} - Normal Run Mode _".format_with(ApplicationParameters.Name, command.GetType().Name)); - command.run(config); + throw new Exception("The implementation of '{0}' does not support listing '{1}'".format_with(config.CommandName, typeof(T).Name)); } + return new List(); + } + else + { + this.Log().Debug("_ {0}:{1} - Normal List Mode _".format_with(ApplicationParameters.Name, command.GetType().Name)); + return command.list(config); + } + } + + public int count(ChocolateyConfiguration config, Container container, bool isConsole, Action parseArgs) + { + var command = find_command(config, container, isConsole, parseArgs) as IListCommand; + if (command == null) + { + if (!string.IsNullOrWhiteSpace(config.CommandName)) + { + throw new Exception("The implementation of '{0}' does not support listing.".format_with(config.CommandName)); + } + return 0; + } + else + { + this.Log().Debug("_ {0}:{1} - Normal Count Mode _".format_with(ApplicationParameters.Name, command.GetType().Name)); + return command.count(config); } } @@ -142,4 +199,5 @@ require admin rights. Only advanced users should run choco w/out an } } -} \ No newline at end of file +} + diff --git a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs index 928203830a..f6a09d5991 100644 --- a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs +++ b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs @@ -79,11 +79,11 @@ public void run(PackageResult packageResult, ChocolateyConfiguration config) { this.Log().Debug(() => " Preparing uninstall key '{0}'".format_with(key.UninstallString)); - if ((!string.IsNullOrWhiteSpace(key.InstallLocation) && !_fileSystem.directory_exists(key.InstallLocation)) || !_registryService.value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation)) + if ((!string.IsNullOrWhiteSpace(key.InstallLocation) && !_fileSystem.directory_exists(key.InstallLocation)) || !_registryService.installer_value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation)) { this.Log().Info(" Skipping auto uninstaller - The application appears to have been uninstalled already by other means."); this.Log().Debug(() => " Searched for install path '{0}' - found? {1}".format_with(key.InstallLocation.escape_curly_braces(), _fileSystem.directory_exists(key.InstallLocation))); - this.Log().Debug(() => " Searched for registry key '{0}' value '{1}' - found? {2}".format_with(key.KeyPath.escape_curly_braces(), ApplicationParameters.RegistryValueInstallLocation, _registryService.value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation))); + this.Log().Debug(() => " Searched for registry key '{0}' value '{1}' - found? {2}".format_with(key.KeyPath.escape_curly_braces(), ApplicationParameters.RegistryValueInstallLocation, _registryService.installer_value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation))); continue; } diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs index 00e1754040..6942bdb443 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs @@ -1,12 +1,12 @@ // Copyright © 2011 - Present 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. @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.services { using System; + using System.Collections.Generic; using System.Linq; using configuration; using infrastructure.services; @@ -44,17 +45,28 @@ public void noop(ChocolateyConfiguration configuration) this.Log().Info("Would have made a change to the configuration."); } - public void source_list(ChocolateyConfiguration configuration) + public IEnumerable source_list(ChocolateyConfiguration configuration) { + var list = new List(); foreach (var source in configFileSettings.Sources) { - this.Log().Info(() => "{0}{1} - {2} {3}| Priority {4}.".format_with( - source.Id, - source.Disabled ? " [Disabled]" : string.Empty, - source.Value, - string.IsNullOrWhiteSpace(source.UserName) ? string.Empty : "(Authenticated)", - source.Priority)); + if (configuration.RegularOutput) { + this.Log().Info(() => "{0}{1} - {2} {3}| Priority {4}.".format_with( + source.Id, + source.Disabled ? " [Disabled]" : string.Empty, + source.Value, + string.IsNullOrWhiteSpace(source.UserName) ? string.Empty : "(Authenticated)", + source.Priority)); + } + list.Add(new ChocolateySource { + Id = source.Id, + Value = source.Value, + Disabled = source.Disabled, + Authenticated = string.IsNullOrWhiteSpace(source.Password), + Priority = source.Priority + }); } + return list; } public void source_add(ChocolateyConfiguration configuration) @@ -284,7 +296,7 @@ public void config_list(ChocolateyConfiguration configuration) ; this.Log().Info(""); this.Log().Info(ChocolateyLoggers.Important, "API Keys"); - this.Log().Info(@"NOTE: Api Keys are not shown through this command. + this.Log().Info(@"NOTE: Api Keys are not shown through this command. Use choco apikey to interact with API keys."); } diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index c68a219932..356bc57480 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -31,10 +31,11 @@ namespace chocolatey.infrastructure.app.services using tolerance; using IFileSystem = filesystem.IFileSystem; - public class ChocolateyPackageService : IChocolateyPackageService + public class ChocolateyPackageService : IChocolateyPackageService { private readonly INugetService _nugetService; private readonly IPowershellService _powershellService; + private readonly IEnumerable _sourceRunners; private readonly IShimGenerationService _shimgenService; private readonly IFileSystem _fileSystem; private readonly IRegistryService _registryService; @@ -44,14 +45,16 @@ public class ChocolateyPackageService : IChocolateyPackageService private readonly IXmlService _xmlService; private readonly IConfigTransformService _configTransformService; - public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, - IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, + public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, + IEnumerable sourceRunners, IShimGenerationService shimgenService, + IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService, IFilesService filesService, IAutomaticUninstallerService autoUninstallerService, IXmlService xmlService, IConfigTransformService configTransformService) { _nugetService = nugetService; _powershellService = powershellService; + _sourceRunners = sourceRunners; _shimgenService = shimgenService; _fileSystem = fileSystem; _registryService = registryService; @@ -62,42 +65,57 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p _configTransformService = configTransformService; } - public void list_noop(ChocolateyConfiguration config) + public void ensure_source_app_installed(ChocolateyConfiguration config) + { + perform_source_runner_action(config, r => r.ensure_source_app_installed(config, (packageResult) => handle_package_result(packageResult, config, CommandNameType.install))); + } + + public int count_run(ChocolateyConfiguration config) { - if (config.Sources.is_equal_to(SpecialSourceType.webpi.to_string())) + return perform_source_runner_function(config, r => r.count_run(config)); + } + + private void perform_source_runner_action(ChocolateyConfiguration config, Action action) + { + var runner = _sourceRunners.FirstOrDefault(r => r.SourceType == config.SourceType); + if (runner != null && action != null) { - //todo: webpi + action.Invoke(runner); } else { - _nugetService.list_noop(config); + this.Log().Warn("No runner was found that implements source type '{0}' or action was missing".format_with(config.SourceType.to_string())); } } + private T perform_source_runner_function(ChocolateyConfiguration config, Func function) + { + var runner = _sourceRunners.FirstOrDefault(r => r.SourceType == 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())); + return default(T); + } + + public void list_noop(ChocolateyConfiguration config) + { + perform_source_runner_action(config, r => r.list_noop(config)); + } + public IEnumerable list_run(ChocolateyConfiguration config) { if (config.RegularOutput) this.Log().Debug(() => "Searching for package information"); - if (config.Sources.is_equal_to(SpecialSourceType.webpi.to_string())) - { - //todo: webpi - //install webpi if not installed - //run the webpi command - this.Log().Warn("Command not yet functional, stay tuned..."); - yield break; - } - else - { - var packages = new List(); + var packages = new List(); - if (config.ListCommand.LocalOnly) - { - config.Sources = ApplicationParameters.PackagesLocation; - config.Prerelease = true; - } - foreach (var package in _nugetService.list_run(config)) + foreach (var package in perform_source_runner_function(config, r => r.list_run(config))) + { + if (config.SourceType == SourceType.normal) { - if (!config.ListCommand.LocalOnly || !config.ListCommand.IncludeRegistryPrograms) + if (!config.ListCommand.IncludeRegistryPrograms) { yield return package; } @@ -107,7 +125,10 @@ public IEnumerable list_run(ChocolateyConfiguration config) packages.Add(package.Package); } } + } + if (config.RegularOutput) + { if (config.ListCommand.LocalOnly && config.ListCommand.IncludeRegistryPrograms) { foreach (var installed in report_registry_programs(config, packages)) @@ -133,39 +154,63 @@ private IEnumerable report_registry_programs(ChocolateyConfigurat this.Log().Info(() => ""); foreach (var key in machineInstalled) { - if (config.RegularOutput) - { - this.Log().Info("{0}|{1}".format_with(key.DisplayName, key.DisplayVersion)); - if (config.Verbose) this.Log().Info(" InstallLocation: {0}{1} Uninstall:{2}".format_with(key.InstallLocation.escape_curly_braces(), Environment.NewLine, key.UninstallString.escape_curly_braces())); - } - count++; + if (config.RegularOutput) + { + this.Log().Info("{0}|{1}".format_with(key.DisplayName, key.DisplayVersion)); + if (config.Verbose) this.Log().Info(" InstallLocation: {0}{1} Uninstall:{2}".format_with(key.InstallLocation.escape_curly_braces(), Environment.NewLine, key.UninstallString.escape_curly_braces())); + } + count++; - yield return new PackageResult(key.DisplayName, key.DisplayName, key.InstallLocation); + yield return new PackageResult(key.DisplayName, key.DisplayName, key.InstallLocation); } if (config.RegularOutput) { - this.Log().Warn(() => @"{0} applications not managed with Chocolatey.".format_with(count)); + this.Log().Warn(() => @"{0} applications not managed with Chocolatey.".format_with(count)); } } public void pack_noop(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for packaging."); + return; + } + _nugetService.pack_noop(config); } public void pack_run(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for packaging."); + return; + } + _nugetService.pack_run(config); } public void push_noop(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for pushing."); + return; + } + _nugetService.push_noop(config); } public void push_run(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for pushing."); + return; + } + _nugetService.push_run(config); } @@ -174,7 +219,13 @@ public void install_noop(ChocolateyConfiguration config) // each package can specify its own configuration values foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, new ConcurrentDictionary()).or_empty_list_if_null()) { - _nugetService.install_noop(packageConfig, (pkg) => _powershellService.install_noop(pkg)); + Action action = null; + if (packageConfig.SourceType == SourceType.normal) + { + action = (pkg) => _powershellService.install_noop(pkg); + } + + perform_source_runner_action(packageConfig, r => r.install_noop(packageConfig, action)); } } @@ -196,7 +247,7 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu if (powerShellRan) { // we don't care about the exit code - if (config.Information.PlatformType == PlatformType.Windows) CommandExecutor.execute("shutdown", "/a", config.CommandExecutionTimeoutSeconds, _fileSystem.get_current_directory(), (s, e) => { }, (s, e) => { }, false, false); + if (config.Information.PlatformType == PlatformType.Windows) CommandExecutor.execute_static("shutdown", "/a", config.CommandExecutionTimeoutSeconds, _fileSystem.get_current_directory(), (s, e) => { }, (s, e) => { }, false, false); } var difference = _registryService.get_differences(before, _registryService.get_installer_keys()); @@ -232,7 +283,7 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu { handle_extension_packages(config, packageResult); } - + _packageInfoService.save_package_information(pkgInfo); ensure_bad_package_path_is_clean(config, packageResult); @@ -246,13 +297,11 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu remove_rollback_if_exists(packageResult); - this.Log().Info(ChocolateyLoggers.Important, " The {0} of {1} was successful.".format_with( commandName.to_string(), packageResult.Name)); + this.Log().Info(ChocolateyLoggers.Important, " The {0} of {1} was successful.".format_with(commandName.to_string(), packageResult.Name)); } public ConcurrentDictionary install_run(ChocolateyConfiguration config) { - //todo:are we installing from an alternate source? If so run that command instead - this.Log().Info(@"Installing the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); this.Log().Info(@"By installing you accept licenses for the packages."); @@ -261,10 +310,13 @@ public ConcurrentDictionary install_run(ChocolateyConfigu foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null()) { - var results = _nugetService.install_run( - packageConfig, - (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install) - ); + Action action = null; + if (packageConfig.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); + } + var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action)); + foreach (var result in results) { packageInstalls.GetOrAdd(result.Key, result.Value); @@ -280,7 +332,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu packageInstalls.Count, installFailures, installWarnings == 0 ? string.Empty : "{0} {1} package(s) had warnings.".format_with(Environment.NewLine, installWarnings), - _fileSystem.combine_paths(ApplicationParameters.LoggingLocation,ApplicationParameters.LoggingFile) + _fileSystem.combine_paths(ApplicationParameters.LoggingLocation, ApplicationParameters.LoggingFile) )); if (installWarnings != 0) @@ -318,6 +370,12 @@ Would have determined packages that are out of date based on what is public void outdated_run(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for outdated."); + return; + } + this.Log().Info(ChocolateyLoggers.Important, @"Outdated Packages Output is package name | current version | available version | pinned? "); @@ -329,7 +387,7 @@ Output is package name | current version | available version | pinned? config.RegularOutput = false; var oudatedPackages = _nugetService.upgrade_noop(config, null); config.RegularOutput = output; - + if (config.RegularOutput) { var upgradeWarnings = oudatedPackages.Count(p => p.Value.Warning); @@ -405,7 +463,13 @@ private IEnumerable get_packages_from_config(string pac public void upgrade_noop(ChocolateyConfiguration config) { - var noopUpgrades = _nugetService.upgrade_noop(config, (pkg) => _powershellService.install_noop(pkg)); + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (pkg) => _powershellService.install_noop(pkg); + } + + var noopUpgrades = perform_source_runner_function(config, r => r.upgrade_noop(config, action)); if (config.RegularOutput) { var upgradeWarnings = noopUpgrades.Count(p => p.Value.Warning); @@ -431,8 +495,6 @@ public void upgrade_noop(ChocolateyConfiguration config) public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config) { - //todo:are we upgrading an alternate source? If so run that command instead - this.Log().Info(@"Upgrading the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); this.Log().Info(@"By upgrading you accept licenses for the packages."); @@ -442,10 +504,13 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu throw new ApplicationException("A packages.config file is only used with installs."); } - var packageUpgrades = _nugetService.upgrade_run( - config, - (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade) - ); + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade); + } + + var packageUpgrades = perform_source_runner_function(config, r => r.upgrade_run(config, action)); var upgradeFailures = packageUpgrades.Count(p => !p.Value.Success); var upgradeWarnings = packageUpgrades.Count(p => p.Value.Warning); @@ -487,7 +552,13 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu public void uninstall_noop(ChocolateyConfiguration config) { - _nugetService.uninstall_noop(config, (pkg) => _powershellService.uninstall_noop(pkg)); + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (pkg) => _powershellService.uninstall_noop(pkg); + } + + perform_source_runner_action(config, r => r.uninstall_noop(config, action)); } public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config) @@ -500,47 +571,13 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi throw new ApplicationException("A packages.config file is only used with installs."); } - var packageUninstalls = _nugetService.uninstall_run( - config, - (packageResult) => - { - if (!_fileSystem.directory_exists(packageResult.InstallLocation)) - { - packageResult.InstallLocation += ".{0}".format_with(packageResult.Package.Version.to_string()); - } - - _shimgenService.uninstall(config, packageResult); - - if (!config.SkipPackageInstallProvider) - { - _powershellService.uninstall(config, packageResult); - } - - if (packageResult.Success) - { - _autoUninstallerService.run(packageResult, config); - } - - // we don't care about the exit code - if (config.Information.PlatformType == PlatformType.Windows) CommandExecutor.execute("shutdown", "/a", config.CommandExecutionTimeoutSeconds, _fileSystem.get_current_directory(), (s, e) => { }, (s, e) => { }, false, false); - - if (packageResult.Success) - { - //todo: v2 clean up package information store for things no longer installed (call it compact?) - uninstall_cleanup(config, packageResult); - } - else - { - this.Log().Error(ChocolateyLoggers.Important, "{0} {1} not successful.".format_with(packageResult.Name, "uninstall")); - handle_unsuccessful_operation(config, packageResult, movePackageToFailureLocation: false, attemptRollback: false); - } + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_uninstall(packageResult, config); + } - if (!packageResult.Success) - { - // throw an error so that NuGet Service doesn't attempt to continue with package removal - throw new ApplicationException("{0} {1} not successful.".format_with(packageResult.Name, "uninstall")); - } - }); + var packageUninstalls = perform_source_runner_function(config, r => r.uninstall_run(config, action)); var uninstallFailures = packageUninstalls.Count(p => !p.Value.Success); this.Log().Warn(() => @"{0}{1} uninstalled {2}/{3} packages. {4} packages failed.{0} See the log for details ({5}).".format_with( @@ -569,13 +606,53 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi return packageUninstalls; } + public void handle_package_uninstall(PackageResult packageResult, ChocolateyConfiguration config) + { + if (!_fileSystem.directory_exists(packageResult.InstallLocation)) + { + packageResult.InstallLocation += ".{0}".format_with(packageResult.Package.Version.to_string()); + } + + _shimgenService.uninstall(config, packageResult); + + if (!config.SkipPackageInstallProvider) + { + _powershellService.uninstall(config, packageResult); + } + + if (packageResult.Success) + { + _autoUninstallerService.run(packageResult, config); + } + + // we don't care about the exit code + if (config.Information.PlatformType == PlatformType.Windows) CommandExecutor.execute_static("shutdown", "/a", config.CommandExecutionTimeoutSeconds, _fileSystem.get_current_directory(), (s, e) => { }, (s, e) => { }, false, false); + + if (packageResult.Success) + { + //todo: v2 clean up package information store for things no longer installed (call it compact?) + uninstall_cleanup(config, packageResult); + } + else + { + this.Log().Error(ChocolateyLoggers.Important, "{0} {1} not successful.".format_with(packageResult.Name, "uninstall")); + handle_unsuccessful_operation(config, packageResult, movePackageToFailureLocation: false, attemptRollback: false); + } + + if (!packageResult.Success) + { + // throw an error so that NuGet Service doesn't attempt to continue with package removal + throw new ApplicationException("{0} {1} not successful.".format_with(packageResult.Name, "uninstall")); + } + } + private void uninstall_cleanup(ChocolateyConfiguration config, PackageResult packageResult) { _packageInfoService.remove_package_information(packageResult.Package); ensure_bad_package_path_is_clean(config, packageResult); remove_rollback_if_exists(packageResult); handle_extension_packages(config, packageResult); - + if (config.Force) { var packageDirectory = _fileSystem.combine_paths(packageResult.InstallLocation); @@ -684,14 +761,14 @@ private void move_bad_package_to_failure_location(PackageResult packageResult) { FaultTolerance.try_catch_with_logging_exception( () => _fileSystem.move_directory(packageResult.InstallLocation, packageResult.InstallLocation.Replace(ApplicationParameters.PackagesLocation, ApplicationParameters.PackageFailuresLocation)), - "Could not move bad package to failure directory It will show as installed.{0} {1}{0} The error".format_with(Environment.NewLine,packageResult.InstallLocation)); + "Could not move bad package to failure directory It will show as installed.{0} {1}{0} The error".format_with(Environment.NewLine, packageResult.InstallLocation)); } } private void rollback_previous_version(ChocolateyConfiguration config, PackageResult packageResult) { if (packageResult.InstallLocation == null) return; - + var rollbackDirectory = packageResult.InstallLocation.Replace(ApplicationParameters.PackagesLocation, ApplicationParameters.PackageBackupLocation); if (!_fileSystem.directory_exists(rollbackDirectory)) { diff --git a/src/chocolatey/infrastructure.app/services/CygwinService.cs b/src/chocolatey/infrastructure.app/services/CygwinService.cs new file mode 100644 index 0000000000..911f87b896 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/CygwinService.cs @@ -0,0 +1,299 @@ +// Copyright © 2011 - Present 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.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + using Microsoft.Win32; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using results; + + /// + /// Alternative Source for Cygwin + /// + /// + /// https://cygwin.com/faq/faq.html#faq.setup.cli + /// + public sealed class CygwinService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private readonly IRegistryService _registryService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string INSTALL_ROOT_TOKEN = "{{installroot}}"; + public const string CYGWIN_PACKAGE = "cygwin"; + private string _rootDirectory = string.Empty; + + private const string APP_NAME = "Cygwin"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"Extracting from file", RegexOptions.Compiled); + public static readonly Regex PackageNameRegex = new Regex(@"/(?<{0}>[^/]*).tar.".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public CygwinService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem, IRegistryService registryService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + _registryService = registryService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_install_dictionary(_installArguments); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + //args.Add("_cmd_c_", new ExternalCommandArgument { ArgumentOption = "/c", Required = true }); + //args.Add("_app_", new ExternalCommandArgument + //{ + // ArgumentOption = "", + // ArgumentValue = _fileSystem.combine_paths(INSTALL_ROOT_TOKEN, "cygwinsetup.exe"), + // QuoteValue = false, + // 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("_root_", new ExternalCommandArgument + { + 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 + }); + + args.Add("_site_", new ExternalCommandArgument + { + 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 + }); + } + + public SourceType SourceType + { + get { return SourceType.cygwin; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + QuietOutput = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig); + + if (!localPackages.Any(p => p.Name.is_equal_to(CYGWIN_PACKAGE))) + { + runnerConfig.PackageNames = CYGWIN_PACKAGE; + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + + set_root_dir_if_not_set(); + } + + public int count_run(ChocolateyConfiguration config) + { + throw new NotImplementedException("Count is not supported for this source runner."); + } + + public void set_root_dir_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_rootDirectory)) return; + + var setupKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Cygwin\\setup"); + if (setupKey != null) + { + _rootDirectory = setupKey.GetValue("rootdir", string.Empty).to_string(); + } + + if (string.IsNullOrWhiteSpace(_rootDirectory)) + { + var binRoot = Environment.GetEnvironmentVariable("ChocolateyBinRoot"); + if (string.IsNullOrWhiteSpace(binRoot)) binRoot = "c:\\tools"; + + _rootDirectory = _fileSystem.combine_paths(binRoot,"cygwin"); + } + } + + public string get_exe(string rootpath) + { + return _fileSystem.combine_paths(rootpath, "cygwinsetup.exe"); + } + + public void list_noop(ChocolateyConfiguration config) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement list".format_with(APP_NAME)); + } + + public IEnumerable list_run(ChocolateyConfiguration config) + { + throw new NotImplementedException("{0} does not implement list".format_with(APP_NAME)); + } + + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + args = args.Replace(INSTALL_ROOT_TOKEN, _rootDirectory); + + return args; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(get_exe(_rootDirectory), args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + + var exitCode = _commandExecutor.execute( + get_exe(_rootDirectory), + argsForPackage, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + }, + updateProcessPath: false, + allowUseWindow: true + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/IChocolateyConfigSettingsService.cs b/src/chocolatey/infrastructure.app/services/IChocolateyConfigSettingsService.cs index 10a0cdb33d..16aa416cd0 100644 --- a/src/chocolatey/infrastructure.app/services/IChocolateyConfigSettingsService.cs +++ b/src/chocolatey/infrastructure.app/services/IChocolateyConfigSettingsService.cs @@ -16,12 +16,13 @@ namespace chocolatey.infrastructure.app.services { using System; + using System.Collections.Generic; using configuration; public interface IChocolateyConfigSettingsService { void noop(ChocolateyConfiguration configuration); - void source_list(ChocolateyConfiguration configuration); + IEnumerable source_list(ChocolateyConfiguration configuration); void source_add(ChocolateyConfiguration configuration); void source_remove(ChocolateyConfiguration configuration); void source_disable(ChocolateyConfiguration configuration); @@ -35,4 +36,4 @@ public interface IChocolateyConfigSettingsService void config_get(ChocolateyConfiguration configuration); void config_set(ChocolateyConfiguration configuration); } -} \ No newline at end of file +} diff --git a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs index 21aaf2fac5..1bdb64965a 100644 --- a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs @@ -25,6 +25,20 @@ namespace chocolatey.infrastructure.app.services /// public interface IChocolateyPackageService { + + /// + /// Ensures the application that controls a source is installed + /// + /// The configuration. + void ensure_source_app_installed(ChocolateyConfiguration config); + + /// + /// Retrieves the count of items that meet the search criteria. + /// + /// + /// + int count_run(ChocolateyConfiguration config); + /// /// Run list in noop mode /// diff --git a/src/chocolatey/infrastructure.app/services/INugetService.cs b/src/chocolatey/infrastructure.app/services/INugetService.cs index ee7b77b125..4a4eb79905 100644 --- a/src/chocolatey/infrastructure.app/services/INugetService.cs +++ b/src/chocolatey/infrastructure.app/services/INugetService.cs @@ -15,28 +15,10 @@ namespace chocolatey.infrastructure.app.services { - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; using configuration; - using results; - public interface INugetService + public interface INugetService : ISourceRunner { - /// - /// Run list in noop mode - /// - /// The configuration. - void list_noop(ChocolateyConfiguration config); - - /// - /// Lists/searches for package against nuget related feeds. - /// - /// The configuration. - /// Should results be logged? - /// - IEnumerable list_run(ChocolateyConfiguration config); - /// /// Run pack in noop mode. /// @@ -62,53 +44,7 @@ public interface INugetService void push_run(ChocolateyConfiguration config); /// - /// Run install in noop mode - /// - /// The configuration. - /// The action to continue with for each noop test install. - void install_noop(ChocolateyConfiguration config, Action continueAction); - - /// - /// Installs packages from NuGet related feeds - /// - /// The configuration. - /// The action to continue with when install is successful. - /// results of installs - ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction); - - /// - /// Run upgrade in noop mode - /// - /// The configuration. - /// The action to continue with for each noop test upgrade. - /// what would have upgraded - ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction); - - /// - /// Upgrades packages from NuGet related feeds - /// - /// The configuration. - /// The action to continue with when upgrade is successful. - /// results of upgrades - ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction); - - /// - /// Run uninstall in noop mode - /// - /// The configuration. - /// The action to continue with for each noop test upgrade. - void uninstall_noop(ChocolateyConfiguration config, Action continueAction); - - /// - /// Uninstalls packages from NuGet related feeds - /// - /// The configuration. - /// The action to continue with when upgrade is successful. - /// results of uninstalls - ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction); - - /// - /// Remove the rollback directory for a package if it exists + /// Remove the rollback directory for a package if it exists /// /// Name of the package. void remove_rollback_directory_if_exists(string packageName); diff --git a/src/chocolatey/infrastructure.app/services/IRegistryService.cs b/src/chocolatey/infrastructure.app/services/IRegistryService.cs index af451bcc01..47adab543a 100644 --- a/src/chocolatey/infrastructure.app/services/IRegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/IRegistryService.cs @@ -15,7 +15,8 @@ namespace chocolatey.infrastructure.app.services { - using domain; + using Microsoft.Win32; + using Registry = domain.Registry; public interface IRegistryService { @@ -23,6 +24,7 @@ public interface IRegistryService Registry get_differences(Registry before, Registry after); void save_to_file(Registry snapshot, string filePath); Registry read_from_file(string filePath); - bool value_exists(string keyPath, string value); + bool installer_value_exists(string keyPath, string value); + RegistryKey get_key(RegistryHive hive, string subKeyPath); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/ISourceRunner.cs b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs new file mode 100644 index 0000000000..c881f10a64 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs @@ -0,0 +1,107 @@ +// Copyright © 2011 - Present 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.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using configuration; + using domain; + using results; + + public interface ISourceRunner + { + /// + /// Gets the source type the source runner implements + /// + /// + /// The type of the source. + /// + SourceType SourceType { get; } + + /// + /// Ensures the application that controls a source is installed + /// + /// The configuration. + /// The action to continue with as part of the install + void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction); + + /// + /// Retrieve the listed packages from the source feed cout + /// + /// The configuration. + /// Packages count + int count_run(ChocolateyConfiguration config); + + /// + /// Run list in noop mode + /// + /// The configuration. + void list_noop(ChocolateyConfiguration config); + + /// + /// Lists/searches for packages from the source feed + /// + /// The configuration. + /// + IEnumerable list_run(ChocolateyConfiguration config); + + /// + /// Run install in noop mode + /// + /// The configuration. + /// The action to continue with for each noop test install. + void install_noop(ChocolateyConfiguration config, Action continueAction); + + /// + /// Installs packages from the source feed + /// + /// The configuration. + /// The action to continue with when install is successful. + /// results of installs + ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction); + + /// + /// Run upgrade in noop mode + /// + /// The configuration. + /// The action to continue with for each noop test upgrade. + ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction); + + /// + /// Upgrades packages from NuGet related feeds + /// + /// The configuration. + /// The action to continue with when upgrade is successful. + /// results of installs + ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction); + + /// + /// Run uninstall in noop mode + /// + /// The configuration. + /// The action to continue with for each noop test upgrade. + void uninstall_noop(ChocolateyConfiguration config, Action continueAction); + + /// + /// Uninstalls packages from NuGet related feeds + /// + /// The configuration. + /// The action to continue with when upgrade is successful. + /// results of installs + ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction); + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 2e50d2b599..d217a8626e 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -65,6 +65,37 @@ public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPack _filesService = filesService; } + public SourceType SourceType + { + get { return SourceType.normal; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + // nothing to do. Nuget.Core is already part of Chocolatey + } + + public int count_run(ChocolateyConfiguration config) + { + int count = 0; + + if (config.ListCommand.LocalOnly) + { + config.Sources = ApplicationParameters.PackagesLocation; + config.Prerelease = true; + } + + int? pageValue = config.ListCommand.Page; + try + { + return NugetList.GetCount(config, _nugetLogger); + } + finally + { + config.ListCommand.Page = pageValue; + } + } + public void list_noop(ChocolateyConfiguration config) { this.Log().Info("{0} would have searched for '{1}' against the following source(s) :\"{2}\"".format_with( @@ -78,6 +109,12 @@ public IEnumerable list_run(ChocolateyConfiguration config) { int count = 0; + if (config.ListCommand.LocalOnly) + { + config.Sources = ApplicationParameters.PackagesLocation; + config.Prerelease = true; + } + if (config.RegularOutput) this.Log().Debug(() => "Running list with the following filter = '{0}'".format_with(config.Input)); if (config.RegularOutput) this.Log().Debug(() => "--- Start of List ---"); foreach (var pkg in NugetList.GetPackages(config, _nugetLogger)) @@ -112,11 +149,11 @@ public IEnumerable list_run(ChocolateyConfiguration config) yield return new PackageResult(package, null, config.Sources); } + if (config.RegularOutput) this.Log().Debug(() => "--- End of List ---"); if (config.RegularOutput) { this.Log().Warn(() => @"{0} packages {1}.".format_with(count, config.ListCommand.LocalOnly ? "installed" : "found")); } - if (config.RegularOutput) this.Log().Debug(() => "--- End of List ---"); } public void pack_noop(ChocolateyConfiguration config) @@ -242,7 +279,7 @@ public void install_noop(ChocolateyConfiguration config, Action c public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { _fileSystem.create_directory_if_not_exists(ApplicationParameters.PackagesLocation); - var packageInstalls = new ConcurrentDictionary(); + var packageInstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); //todo: handle all @@ -426,7 +463,7 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, bool performAction) { _fileSystem.create_directory_if_not_exists(ApplicationParameters.PackagesLocation); - var packageInstalls = new ConcurrentDictionary(); + var packageInstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; var packageManager = NugetCommon.GetPackageManager( @@ -837,7 +874,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, bool performAction) { - var packageUninstalls = new ConcurrentDictionary(); + var packageUninstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; var packageManager = NugetCommon.GetPackageManager(config, _nugetLogger, @@ -1082,7 +1119,7 @@ private void set_package_names_if_all_is_specified(ChocolateyConfiguration confi config.QuietOutput = true; config.PackageNames = list_run(config).Select(p => p.Name).@join(ApplicationParameters.PackageNamesSeparator); - + config.QuietOutput = quiet; config.Input = input; config.Noop = noop; diff --git a/src/chocolatey/infrastructure.app/services/PythonService.cs b/src/chocolatey/infrastructure.app/services/PythonService.cs new file mode 100644 index 0000000000..f8be23e18b --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/PythonService.cs @@ -0,0 +1,546 @@ +namespace chocolatey.infrastructure.app.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + using Microsoft.Win32; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using results; + + /// + /// Alternative Source for Installing Python packages + /// + public sealed class PythonService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private readonly IRegistryService _registryService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string LOG_LEVEL_TOKEN = "{{loglevel}}"; + private const string FORCE_TOKEN = "{{force}}"; + public const string PYTHON_PACKAGE = "python"; + private string _exePath = string.Empty; + + private const string APP_NAME = "Python"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"Successfully installed", RegexOptions.Compiled); + public static readonly Regex UninstalledRegex = new Regex(@"Successfully uninstalled", RegexOptions.Compiled); + public static readonly Regex PackageNameRegex = new Regex(@"\s(?<{0}>[^-\s]*)-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + public static readonly Regex ErrorRegex = new Regex(@"Error:", RegexOptions.Compiled); + public static readonly Regex ErrorNotFoundRegex = new Regex(@"Could not find any downloads that", RegexOptions.Compiled); + + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _upgradeArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _uninstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public PythonService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem, IRegistryService registryService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + _registryService = registryService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); + set_upgrade_dictionary(_upgradeArguments); + set_uninstall_dictionary(_uninstallArguments); + } + + /// + /// Sets list dictionary + /// + private void set_list_dictionary(IDictionary args) + { + set_common_args(args); + args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "list", Required = true }); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + set_common_args(args); + + 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 + }); + } + + /// + /// Sets install dictionary + /// + private void set_upgrade_dictionary(IDictionary args) + { + set_common_args(args); + + 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 + }); + } + + /// + /// Sets uninstall dictionary + /// + private void set_uninstall_dictionary(IDictionary args) + { + set_common_args(args); + + args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "uninstall", Required = true }); + args.Add("_confirm_", new ExternalCommandArgument { ArgumentOption = "-y", Required = true }); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + } + + private void set_common_args(IDictionary args) + { + args.Add("_loglevel_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = LOG_LEVEL_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + + args.Add("_force_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = FORCE_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + + + } + + public SourceType SourceType + { + get { return SourceType.python; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + //ensure at least python 2.7.9 is installed + var python = _fileSystem.get_executable_path("python"); + //python -V + + if (python.is_equal_to("python")) + { + var runnerConfig = new ChocolateyConfiguration + { + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + QuietOutput = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig); + + if (!localPackages.Any(p => p.Name.is_equal_to(PYTHON_PACKAGE))) + { + runnerConfig.PackageNames = PYTHON_PACKAGE; + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + } + } + + public int count_run(ChocolateyConfiguration config) + { + throw new NotImplementedException("Count is not supported for this source runner."); + } + + public void set_executable_path_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_exePath)) return; + + var python = _fileSystem.get_executable_path("python"); + + var pipPath = string.Empty; + if (!python.is_equal_to("python")) + { + pipPath = _fileSystem.combine_paths(_fileSystem.get_directory_name(python), "Scripts", "pip.exe"); + if (_fileSystem.file_exists(pipPath)) + { + _exePath = pipPath; + return; + } + } + + var topLevelPath = string.Empty; + var python34PathKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Python\\PythonCore\\3.4\\InstallPath"); + if (python34PathKey != null) + { + topLevelPath = python34PathKey.GetValue("", string.Empty).to_string(); + } + if (string.IsNullOrWhiteSpace(topLevelPath)) + { + var python27PathKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Python\\PythonCore\\2.7\\InstallPath"); + if (python27PathKey != null) + { + topLevelPath = python27PathKey.GetValue("", string.Empty).to_string(); + } + } + + if (string.IsNullOrWhiteSpace(topLevelPath)) + { + var binRoot = Environment.GetEnvironmentVariable("ChocolateyBinRoot"); + if (string.IsNullOrWhiteSpace(binRoot)) binRoot = "c:\\tools"; + + topLevelPath = _fileSystem.combine_paths(binRoot, "python"); + } + + pipPath = _fileSystem.combine_paths(_fileSystem.get_directory_name(topLevelPath), "Scripts", "pip.exe"); + if (_fileSystem.file_exists(pipPath)) + { + _exePath = pipPath; + } + + if (string.IsNullOrWhiteSpace(_exePath)) throw new FileNotFoundException("Unable to find pip"); + } + + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + args = args.Replace(LOG_LEVEL_TOKEN, config.Debug ? "-vvv" : ""); + + if (config.CommandName.is_equal_to("intall")) + { + args = args.Replace(FORCE_TOKEN, config.Force ? "--ignore-installed" : ""); + } + else if (config.CommandName.is_equal_to("upgrade")) + { + args = args.Replace(FORCE_TOKEN, config.Force ? "--force-reinstall" : ""); + } + else + { + args = args.Replace(FORCE_TOKEN, ""); + } + + return args; + } + + public void list_noop(ChocolateyConfiguration config) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public IEnumerable list_run(ChocolateyConfiguration config) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + var packageResults = new List(); + + Environment.ExitCode = _commandExecutor.execute( + _exePath, + args, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (!config.QuietOutput) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false, + allowUseWindow: true + ); + + return packageResults; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var pkgName = packageToInstall; + if (!string.IsNullOrWhiteSpace(config.Version)) + { + pkgName = "{0}=={1}".format_with(packageToInstall, config.Version); + } + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, pkgName); + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + }, + updateProcessPath: false, + allowUseWindow: true + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _upgradeArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _upgradeArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var pkgName = packageToInstall; + if (!string.IsNullOrWhiteSpace(config.Version)) + { + pkgName = "{0}=={1}".format_with(packageToInstall, config.Version); + } + + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, pkgName); + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + }, + updateProcessPath: false, + allowUseWindow: true + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, packageToInstall)); + } + + if (UninstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + }, + updateProcessPath: false, + allowUseWindow: true + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index bc39b54721..fc19d2ad99 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -35,6 +35,7 @@ public sealed class RegistryService : IRegistryService { private readonly IXmlService _xmlService; private readonly IFileSystem _fileSystem; + private readonly bool _logOutput = false; public RegistryService(IXmlService xmlService, IFileSystem fileSystem) { @@ -82,13 +83,16 @@ public Registry get_installer_keys() registryKey.Dispose(); } - //Console.WriteLine(""); - //Console.WriteLine("A total of {0} unrecognized apps".format_with(snapshot.RegistryKeys.Where((p) => p.InstallerType == InstallerType.Unknown).Count())); - //Console.WriteLine(""); + if (_logOutput) + { + Console.WriteLine(""); + Console.WriteLine("A total of {0} unrecognized apps".format_with(snapshot.RegistryKeys.Where((p) => p.InstallerType == InstallerType.Unknown && p.is_in_programs_and_features()).Count())); + Console.WriteLine(""); - //Console.WriteLine(""); - //Console.WriteLine("A total of {0} of {1} are programs and features apps".format_with(snapshot.RegistryKeys.Where((p) => p.is_in_programs_and_features()).Count(), snapshot.RegistryKeys.Count)); - //Console.WriteLine(""); + Console.WriteLine(""); + Console.WriteLine("A total of {0} of {1} are programs and features apps".format_with(snapshot.RegistryKeys.Where((p) => p.is_in_programs_and_features()).Count(), snapshot.RegistryKeys.Count)); + Console.WriteLine(""); + } return snapshot; } @@ -192,21 +196,23 @@ public void evaluate_keys(RegistryKey key, Registry snapshot) appKey.InstallerType = InstallerType.Custom; } - //if (appKey.InstallerType == InstallerType.Msi) - //{ - //Console.WriteLine(""); - //if (!string.IsNullOrWhiteSpace(appKey.UninstallString)) - //{ - // Console.WriteLine(appKey.UninstallString.to_string().Split(new[] { " /", " -" }, StringSplitOptions.RemoveEmptyEntries)[0]); - // key.UninstallString.to_string().Split(new[] { " /", " -" }, StringSplitOptions.RemoveEmptyEntries); - //} - //foreach (var name in key.GetValueNames()) - //{ - // var kind = key.GetValueKind(name); - // var value = key.GetValue(name); - // Console.WriteLine("key - {0}, name - {1}, kind - {2}, value - {3}".format_with(key.Name, name, kind, value.to_string())); - //} - //} + if (_logOutput) + { + if (appKey.is_in_programs_and_features() && appKey.InstallerType == InstallerType.Unknown) + { + foreach (var name in key.GetValueNames()) + { + //var kind = key.GetValueKind(name); + var value = key.GetValue(name); + if (name.is_equal_to("QuietUninstallString") || name.is_equal_to("UninstallString")) + { + Console.WriteLine("key - {0}|{1}={2}|Type detected={3}".format_with(key.Name, name, value.to_string(), appKey.InstallerType.to_string())); + } + + //Console.WriteLine("key - {0}, name - {1}, kind - {2}, value - {3}".format_with(key.Name, name, kind, value.to_string())); + } + } + } snapshot.RegistryKeys.Add(appKey); } @@ -226,12 +232,9 @@ public void save_to_file(Registry snapshot, string filePath) _xmlService.serialize(snapshot, filePath); } - public bool value_exists(string keyPath, string value) + public bool installer_value_exists(string keyPath, string value) { - //todo: make this check less crazy... return get_installer_keys().RegistryKeys.Any(k => k.KeyPath == keyPath); - - //return Microsoft.Win32.Registry.GetValue(keyPath, value, null) != null; } public Registry read_from_file(string filePath) @@ -243,5 +246,30 @@ public Registry read_from_file(string filePath) return _xmlService.deserialize(filePath); } + + public RegistryKey get_key(RegistryHive hive, string subKeyPath) + { + IList keyLocations = new List(); + if (Environment.Is64BitOperatingSystem) + { + keyLocations.Add(RegistryKey.OpenBaseKey(hive, RegistryView.Registry64)); + } + + keyLocations.Add(RegistryKey.OpenBaseKey(hive, RegistryView.Registry32)); + + foreach (var topLevelRegistryKey in keyLocations) + { + using (topLevelRegistryKey) + { + var key = topLevelRegistryKey.OpenSubKey(subKeyPath, RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey); + if (key != null) + { + return key; + } + } + } + + return null; + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs new file mode 100644 index 0000000000..c8ae9462a0 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -0,0 +1,282 @@ +namespace chocolatey.infrastructure.app.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + using configuration; + using domain; + using infrastructure.commands; + using logging; + using results; + + public sealed class RubyGemsService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string EXE_PATH = "cmd.exe"; + private const string APP_NAME = "Ruby Gems"; + public const string RUBY_PORTABLE_PACKAGE = "ruby.portable"; + public const string RUBY_PACKAGE = "ruby"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstallingRegex = new Regex(@"Fetching:", RegexOptions.Compiled); + public static readonly Regex InstalledRegex = new Regex(@"Successfully installed", RegexOptions.Compiled); + public static readonly Regex ErrorNotFoundRegex = new Regex(@"ERROR: Could not find a valid gem", RegexOptions.Compiled); + public static readonly Regex PackageNameFetchingRegex = new Regex(@"Fetching: (?<{0}>.*)\-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + 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); + + public RubyGemsService(ICommandExecutor commandExecutor, INugetService nugetService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); + } + + /// + /// Sets list dictionary + /// + private void set_list_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 = "list", Required = true}); + } + + /// + /// Sets install dictionary + /// + 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("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "package name ", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + + args.Add("Force", new ExternalCommandArgument + { + ArgumentOption = "-f ", + QuoteValue = false, + Required = false + }); + + args.Add("Version", new ExternalCommandArgument + { + ArgumentOption = "--version ", + QuoteValue = false, + Required = false + }); + } + + public SourceType SourceType + { + get { return SourceType.ruby; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + PackageNames = RUBY_PORTABLE_PACKAGE, + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + QuietOutput = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig); + + if (!localPackages.Any(p => p.Name.is_equal_to(RUBY_PACKAGE) || p.Name.is_equal_to(RUBY_PORTABLE_PACKAGE))) + { + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + } + + public int count_run(ChocolateyConfiguration config) + { + throw new NotImplementedException("Count is not supported for this source runner."); + } + + public void list_noop(ChocolateyConfiguration config) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); + } + + public IEnumerable list_run(ChocolateyConfiguration config) + { + var packageResults = new List(); + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + + Environment.ExitCode = _commandExecutor.execute( + EXE_PATH, + args, + config.CommandExecutionTimeoutSeconds, + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (!config.QuietOutput) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false + ); + + return packageResults; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); + args = args.Replace(PACKAGE_NAME_TOKEN, config.PackageNames.Replace(';', ',')); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + var exitCode = _commandExecutor.execute( + EXE_PATH, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (InstallingRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameFetchingRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + this.Log().Info(ChocolateyLoggers.Important, "{0}".format_with(packageName)); + return; + } + + //if (string.IsNullOrWhiteSpace(packageName)) return; + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameInstalledRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + var packageName = get_value_from_output(logMessage, PackageNameErrorRegex, PACKAGE_NAME_GROUP); + + if (ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement uninstall".format_with(APP_NAME)); + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 25ada7c261..e52c23e52e 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -18,28 +18,36 @@ namespace chocolatey.infrastructure.app.services using System; using System.Collections.Concurrent; using System.Collections.Generic; + using System.Linq; using System.Text.RegularExpressions; using configuration; + using domain; using infrastructure.commands; using logging; using results; - public interface IWebPiService - { - } - - //todo this is the old nuget.exe installer code that needs cleaned up for webpi - public class WebPiService : IWebPiService + public sealed class WebPiService : ISourceRunner { private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; - private readonly string _webPiExePath = "webpicmd"; //ApplicationParameters.Tools.NugetExe; - private readonly IDictionary _webPiListArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - private readonly IDictionary _webPiInstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private const string EXE_PATH = "webpicmd.exe"; + private const string APP_NAME = "Web Platform Installer"; + public const string WEB_PI_PACKAGE = "webpicmd"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + 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); + + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - public WebPiService(ICommandExecutor commandExecutor) + public WebPiService(ICommandExecutor commandExecutor, INugetService nugetService) { _commandExecutor = commandExecutor; + _nugetService = nugetService; set_cmd_args_dictionaries(); } @@ -48,95 +56,187 @@ public WebPiService(ICommandExecutor commandExecutor) /// private void set_cmd_args_dictionaries() { - //set_webpi_list_dictionary(); - set_webpi_install_dictionary(); + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); } /// - /// Sets webpicmd install dictionary + /// Sets list dictionary /// - private void set_webpi_install_dictionary() + private void set_list_dictionary(IDictionary args) { - _webPiInstallArguments.Add("_install_", new ExternalCommandArgument {ArgumentOption = "install", Required = true}); - _webPiInstallArguments.Add("_package_name_", new ExternalCommandArgument {ArgumentOption = PACKAGE_NAME_TOKEN, Required = true}); - _webPiInstallArguments.Add("Version", new ExternalCommandArgument {ArgumentOption = "-version ",}); - _webPiInstallArguments.Add("_output_directory_", new ExternalCommandArgument + args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/List", Required = true}); + args.Add("_list_option_", new ExternalCommandArgument {ArgumentOption = "/ListOption:All", Required = true}); + } + + /// + /// Sets install dictionary + /// + 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("_package_name_", new ExternalCommandArgument { - ArgumentOption = "-outputdirectory ", - ArgumentValue = "{0}".format_with(ApplicationParameters.PackagesLocation), - QuoteValue = true, + ArgumentOption = "/Products:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, Required = true }); - _webPiInstallArguments.Add("Sources", new ExternalCommandArgument {ArgumentOption = "-source ", QuoteValue = true}); - _webPiInstallArguments.Add("Prerelease", new ExternalCommandArgument {ArgumentOption = "-prerelease"}); - _webPiInstallArguments.Add("_non_interactive_", new ExternalCommandArgument {ArgumentOption = "-noninteractive", Required = true}); - _webPiInstallArguments.Add("_no_cache_", new ExternalCommandArgument {ArgumentOption = "-nocache", Required = true}); } - public ConcurrentDictionary install_run(ChocolateyConfiguration configuration, Action continueAction) + public SourceType SourceType { - var packageInstalls = new ConcurrentDictionary(); + get { return SourceType.webpi; } + } - var args = ExternalCommandArgsBuilder.build_arguments(configuration, _webPiInstallArguments); + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + PackageNames = WEB_PI_PACKAGE, + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + QuietOutput = true, + }; + runnerConfig.ListCommand.LocalOnly = true; - foreach (var packageToInstall in configuration.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + var localPackages = _nugetService.list_run(runnerConfig); + + if (!localPackages.Any(p => p.Name.is_equal_to(WEB_PI_PACKAGE))) { - var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); - var exitCode = _commandExecutor.execute( - _webPiExePath, argsForPackage, configuration.CommandExecutionTimeoutSeconds, - (s, e) => - { - var logMessage = e.Data; - if (string.IsNullOrWhiteSpace(logMessage)) return; - this.Log().Debug(() => " [WebPI] {0}".format_with(logMessage)); + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; - var packageName = get_value_from_output(logMessage, ApplicationParameters.OutputParser.Nuget.PackageName, ApplicationParameters.OutputParser.Nuget.PACKAGE_NAME_GROUP); - var packageVersion = get_value_from_output(logMessage, ApplicationParameters.OutputParser.Nuget.PackageVersion, ApplicationParameters.OutputParser.Nuget.PACKAGE_VERSION_GROUP); + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + } - if (ApplicationParameters.OutputParser.Nuget.ResolvingDependency.IsMatch(logMessage)) - { - return; - } + public int count_run(ChocolateyConfiguration config) + { + throw new NotImplementedException("Count is not supported for this source runner."); + } - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, packageVersion, _webPiInstallArguments["_output_directory_"].ArgumentValue)); + public void list_noop(ChocolateyConfiguration config) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); + } - if (ApplicationParameters.OutputParser.Nuget.NotInstalled.IsMatch(logMessage)) - { - this.Log().Error("{0} not installed: {1}".format_with(packageName, logMessage)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + public IEnumerable list_run(ChocolateyConfiguration config) + { + var packageResults = new List(); + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); - return; - } + //var whereToStartRecording = "---"; + //var whereToStopRecording = "--"; + //var recordingValues = false; - if (ApplicationParameters.OutputParser.Nuget.Installing.IsMatch(logMessage)) - { - return; - } + Environment.ExitCode = _commandExecutor.execute( + EXE_PATH, + args, + config.CommandExecutionTimeoutSeconds, + workingDirectory: ApplicationParameters.ShimsLocation, + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (!config.QuietOutput) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + + //if (recordingValues) + //{ + // var lineParts = logMessage.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + // if (lineParts.Length > 1) + // { + // var pkgResult = new PackageResult(lineParts[0], null, null); + // packageResults.GetOrAdd(lineParts[0], pkgResult); + // } + //} - if (string.IsNullOrWhiteSpace(packageName)) return; + //if (logMessage.Contains(whereToStartRecording)) recordingValues = true; + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false, + allowUseWindow: true + ); + + return packageResults; + } - this.Log().Info(ChocolateyLoggers.Important, "{0} {1}".format_with(packageName, !string.IsNullOrWhiteSpace(packageVersion) ? "v" + packageVersion : string.Empty)); + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); + args = args.Replace(PACKAGE_NAME_TOKEN, config.PackageNames.Replace(';', ',')); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + var exitCode = _commandExecutor.execute( + EXE_PATH, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + ApplicationParameters.ShimsLocation, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); - if (ApplicationParameters.OutputParser.Nuget.AlreadyInstalled.IsMatch(logMessage) && !configuration.Force) + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + if (AlreadyInstalledRegex.IsMatch(logMessage)) { results.Messages.Add(new ResultMessage(ResultType.Inconclusive, packageName)); - this.Log().Warn(" Already installed."); - this.Log().Warn(ChocolateyLoggers.Important, " Use -force if you want to reinstall.".format_with(Environment.NewLine)); + this.Log().Warn(ChocolateyLoggers.Important, " [{0}] {1} already installed or doesn't exist. --force has no effect.".format_with(APP_NAME, string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); return; } - results.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - if (continueAction != null) + if (InstallingRegex.IsMatch(logMessage)) { - continueAction.Invoke(results); + this.Log().Info(ChocolateyLoggers.Important, "{0}".format_with(packageName)); + return; + } + + if (InstalledRegex.IsMatch(logMessage)) + { + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); } }, (s, e) => { if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Error(() => "{0}".format_with(e.Data)); + this.Log().Error(() => "[{0}] {1}".format_with(APP_NAME, e.Data)); }, - updateProcessPath: false + updateProcessPath: false, + allowUseWindow:true ); if (exitCode != 0) @@ -144,7 +244,29 @@ public ConcurrentDictionary install_run(ChocolateyConfigu Environment.ExitCode = exitCode; } } - return packageInstalls; + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement uninstall".format_with(APP_NAME)); } /// diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs new file mode 100644 index 0000000000..265e2665ec --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -0,0 +1,403 @@ +// Copyright © 2011 - Present 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.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using results; + + /// + /// Alternative Source for Enabling Windows Features + /// + /// + /// https://technet.microsoft.com/en-us/library/hh825265.aspx?f=255&MSPPError=-2147217396 + /// Win 7 - https://technet.microsoft.com/en-us/library/dd744311.aspx + /// Maybe Win2003/2008 - http://www.wincert.net/forum/files/file/8-deployment-image-servicing-and-management-dism/ | http://wincert.net/leli55PK/DISM/ + /// + public sealed class WindowsFeatureService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private const string ALL_TOKEN = "{{all}}"; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string LOG_LEVEL_TOKEN = "{{loglevel}}"; + private const string LOG_LEVEL_INFO = "3"; + private const string LOG_LEVEL_DEBUG = "4"; + private const string FEATURES_VALUE = "/Get-Features"; + private const string FORMAT_VALUE = "/Format:Table"; + private string _exePath = string.Empty; + + private static readonly IList _exeLocations = new List + { + Environment.ExpandEnvironmentVariables("%systemroot%\\sysnative\\dism.exe"), + Environment.ExpandEnvironmentVariables("%systemroot%\\System32\\dism.exe"), + "dism.exe" + }; + + private const string APP_NAME = "Windows Features"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"The operation completed successfully.", RegexOptions.Compiled); + public static readonly Regex ErrorRegex = new Regex(@"Error:", RegexOptions.Compiled); + public static readonly Regex ErrorNotFoundRegex = new Regex(@"Feature name .* is unknown", RegexOptions.Compiled); + + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _uninstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public WindowsFeatureService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); + set_uninstall_dictionary(_uninstallArguments); + } + + /// + /// Sets list dictionary + /// + 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}); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + set_common_args(args); + + args.Add("_all_", new ExternalCommandArgument {ArgumentOption = ALL_TOKEN, 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 + }); + } + + /// + /// Sets uninstall dictionary + /// + private void set_uninstall_dictionary(IDictionary args) + { + set_common_args(args); + + // uninstall feature completely in 8/2012+ - /Remove + // would need /source to bring it back + + args.Add("_feature_", new ExternalCommandArgument {ArgumentOption = "/Disable-Feature", Required = true}); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "/FeatureName:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); + } + + private void set_common_args(IDictionary args) + { + 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 + }); + + args.Add("_no_restart_", new ExternalCommandArgument {ArgumentOption = "/NoRestart", Required = true}); + } + + public SourceType SourceType + { + get { return SourceType.windowsfeatures; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + set_executable_path_if_not_set(); + } + + public int count_run(ChocolateyConfiguration config) + { + throw new NotImplementedException("Count is not supported for this source runner."); + } + + public void set_executable_path_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_exePath)) return; + + foreach (var location in _exeLocations) + { + if (_fileSystem.file_exists(location)) + { + _exePath = location; + break; + } + } + + if (string.IsNullOrWhiteSpace(_exePath)) throw new FileNotFoundException("Unable to find suitable location for the executable. Searched the following locations: '{0}'".format_with(string.Join("; ", _exeLocations))); + } + + public void list_noop(ChocolateyConfiguration config) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public IEnumerable list_run(ChocolateyConfiguration config) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + var packageResults = new List(); + + Environment.ExitCode = _commandExecutor.execute( + _exePath, + args, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (!config.QuietOutput) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false, + allowUseWindow: true + ); + + return packageResults; + } + + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + // at least Windows 8/2012 + if (config.Information.PlatformVersion.Major >= 6 && config.Information.PlatformVersion.Minor >= 2) + { + args = args.Replace(ALL_TOKEN, "/All"); + } + else + { + args = args.Replace(ALL_TOKEN, string.Empty); + } + + if (!string.IsNullOrWhiteSpace(config.Input)) + { + args = args.Replace(FEATURES_VALUE, "/Get-FeatureInfo").Replace(FORMAT_VALUE, "/FeatureName:{0}".format_with(config.Input)); + } + + args = args.Replace(LOG_LEVEL_TOKEN, config.Debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO); + + return args; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var packageName = packageToInstall; + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageName); + + //todo: detect windows feature is already enabled + /* + $checkStatement=@" + `$dismInfo=(cmd /c `"$dism /Online /Get-FeatureInfo /FeatureName:$packageName`") + if(`$dismInfo -contains 'State : Enabled') {return} + if(`$dismInfo -contains 'State : Enable Pending') {return} + "@ + */ + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + }, + updateProcessPath: false, + allowUseWindow: true + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var packageName = packageToInstall; + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageName); + + //todo: detect windows feature is already disabled + /* + $checkStatement=@" + `$dismInfo=(cmd /c `"$dism /Online /Get-FeatureInfo /FeatureName:$packageName`") + if(`$dismInfo -contains 'State : Disabled') {return} + if(`$dismInfo -contains 'State : Disable Pending') {return} + "@ + */ + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + _fileSystem.get_current_directory(), + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + }, + updateProcessPath: false, + allowUseWindow: true + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure/adapters/Environment.cs b/src/chocolatey/infrastructure/adapters/Environment.cs index de030bb1d6..fa6b50b782 100644 --- a/src/chocolatey/infrastructure/adapters/Environment.cs +++ b/src/chocolatey/infrastructure/adapters/Environment.cs @@ -1,12 +1,12 @@ // Copyright © 2011 - Present 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. @@ -39,9 +39,14 @@ public string NewLine get { return System.Environment.NewLine; } } + public string GetEnvironmentVariable(string variable) + { + return System.Environment.GetEnvironmentVariable(variable); + } + public void SetEnvironmentVariable(string variable, string value) { System.Environment.SetEnvironmentVariable(variable, value); } } -} \ No newline at end of file +} diff --git a/src/chocolatey/infrastructure/adapters/IEnvironment.cs b/src/chocolatey/infrastructure/adapters/IEnvironment.cs index 1abb1042eb..91eb0bbb23 100644 --- a/src/chocolatey/infrastructure/adapters/IEnvironment.cs +++ b/src/chocolatey/infrastructure/adapters/IEnvironment.cs @@ -1,12 +1,12 @@ // Copyright © 2011 - Present 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. @@ -63,6 +63,13 @@ public interface IEnvironment /// 1 string NewLine { get; } + /// + /// Gets the environment variable. + /// + /// The variable. + /// + string GetEnvironmentVariable(string variable); + /// /// Creates, modifies, or deletes an environment variable stored in the current process. /// diff --git a/src/chocolatey/infrastructure/commands/CommandExecutor.cs b/src/chocolatey/infrastructure/commands/CommandExecutor.cs index e4548eb3a8..7da0340606 100644 --- a/src/chocolatey/infrastructure/commands/CommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/CommandExecutor.cs @@ -49,7 +49,7 @@ public static void initialize_with(Lazy file_system, Func public int execute(string process, string arguments, int waitForExitInSeconds) { - return execute(process, arguments, waitForExitInSeconds, file_system.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty))); + return execute(process, arguments, waitForExitInSeconds, file_system.get_directory_name(file_system.get_current_assembly_path())); } public int execute( @@ -64,7 +64,7 @@ public int execute( return execute(process, arguments, waitForExitInSeconds, - file_system.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), + file_system.get_directory_name(file_system.get_current_assembly_path()), stdOutAction, stdErrAction, updateProcessPath, @@ -77,7 +77,28 @@ public int execute(string process, string arguments, int waitForExitInSeconds, s return execute(process, arguments, waitForExitInSeconds, workingDirectory, null, null, updateProcessPath: true, allowUseWindow: false); } - public static int execute(string process, + public int execute(string process, + string arguments, + int waitForExitInSeconds, + string workingDirectory, + Action stdOutAction, + Action stdErrAction, + bool updateProcessPath, + bool allowUseWindow + ) + { + return execute_static(process, + arguments, + waitForExitInSeconds, + file_system.get_directory_name(Assembly.GetExecutingAssembly().Location), + stdOutAction, + stdErrAction, + updateProcessPath, + allowUseWindow + ); + } + + public static int execute_static(string process, string arguments, int waitForExitInSeconds, string workingDirectory, @@ -106,15 +127,11 @@ bool allowUseWindow UseShellExecute = false, WorkingDirectory = workingDirectory, RedirectStandardOutput = true, - RedirectStandardError = true, + RedirectStandardError = true, CreateNoWindow = !allowUseWindow, + WindowStyle = ProcessWindowStyle.Minimized, }; - if (allowUseWindow) - { - psi.WindowStyle = ProcessWindowStyle.Minimized; - } - using (var p = initialize_process()) { p.StartInfo = psi; diff --git a/src/chocolatey/infrastructure/commands/ICommandExecutor.cs b/src/chocolatey/infrastructure/commands/ICommandExecutor.cs index 5e8a89dfa2..ac09ed6796 100644 --- a/src/chocolatey/infrastructure/commands/ICommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/ICommandExecutor.cs @@ -30,6 +30,17 @@ int execute( Action stdOutAction, Action stdErrAction, bool updateProcessPath + ); + + int execute( + string process, + string arguments, + int waitForExitInSeconds, + string workingDirectory, + Action stdOutAction, + Action stdErrAction, + bool updateProcessPath, + bool allowUseWindow ); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure/commands/IListCommand.cs b/src/chocolatey/infrastructure/commands/IListCommand.cs index b61daf5504..caf6f7d4f2 100644 --- a/src/chocolatey/infrastructure/commands/IListCommand.cs +++ b/src/chocolatey/infrastructure/commands/IListCommand.cs @@ -1,4 +1,4 @@ -// Copyright © 2011 - Present RealDimensions Software, LLC +// Copyright © 2011 - Present 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. @@ -18,8 +18,13 @@ namespace chocolatey.infrastructure.commands using System.Collections.Generic; using app.configuration; - public interface IListCommand : ICommand + public interface IListCommand : ICommand { - IEnumerable list(ChocolateyConfiguration configuration); + int count(ChocolateyConfiguration config); + } + + public interface IListCommand : IListCommand + { + IEnumerable list(ChocolateyConfiguration config); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure/commands/PowershellExecutor.cs b/src/chocolatey/infrastructure/commands/PowershellExecutor.cs index c81552da50..124b2973da 100644 --- a/src/chocolatey/infrastructure/commands/PowershellExecutor.cs +++ b/src/chocolatey/infrastructure/commands/PowershellExecutor.cs @@ -56,11 +56,11 @@ Action stdErrAction //-NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%DIR%chocolatey.ps1' %PS_ARGS%" string arguments = "-NoProfile -NoLogo -ExecutionPolicy Bypass -Command \"{0}\"".format_with(command); - return CommandExecutor.execute( + return CommandExecutor.execute_static( _powershell, arguments, waitForExitSeconds, - workingDirectory: fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), + workingDirectory: fileSystem.get_directory_name(fileSystem.get_current_assembly_path()), stdOutAction: stdOutAction, stdErrAction: stdErrAction, updateProcessPath: true, diff --git a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs index e0e3887d23..5a7f6372f7 100644 --- a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs @@ -17,14 +17,19 @@ namespace chocolatey.infrastructure.filesystem { using System; using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; + using adapters; + using app; using platforms; using tolerance; + using Assembly = adapters.Assembly; + using Environment = adapters.Environment; /// /// Implementation of IFileSystem for Dot Net @@ -33,6 +38,7 @@ namespace chocolatey.infrastructure.filesystem public sealed class DotNetFileSystem : IFileSystem { private readonly int TIMES_TO_TRY_OPERATION = 3; + private static Lazy environment_initializer = new Lazy(() => new Environment()); private void allow_retries(Action action) { @@ -43,6 +49,17 @@ private void allow_retries(Action action) increaseRetryByMilliseconds: 100); } + [EditorBrowsable(EditorBrowsableState.Never)] + public void initialize_with(Lazy environment) + { + environment_initializer = environment; + } + + private static IEnvironment Environment + { + get { return environment_initializer.Value; } + } + #region Path public string combine_paths(string leftItem, params string[] rightItems) @@ -83,6 +100,57 @@ public char get_path_directory_separator_char() return Path.DirectorySeparatorChar; } + public char get_path_separator() + { + return Path.PathSeparator; + } + + public string get_executable_path(string executableName) + { + if (string.IsNullOrWhiteSpace(executableName)) return string.Empty; + + var isWindows = Platform.get_platform() == PlatformType.Windows; + IList extensions = new List(); + + if (get_file_name_without_extension(executableName).is_equal_to(executableName) && isWindows) + { + var pathExtensions = Environment.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions).to_string().Split(new[] {ApplicationParameters.Environment.PathExtensionsSeparator}, StringSplitOptions.RemoveEmptyEntries); + foreach (var extension in pathExtensions.or_empty_list_if_null()) + { + extensions.Add(extension.StartsWith(".") ? extension : ".{0}".format_with(extension)); + } + } + + // Always add empty, for when the executable name is enough. + extensions.Add(string.Empty); + + // Gets the path to an executable based on looking in current + // working directory, next to the running process, then among the + // derivatives of Path and Pathext variables, applied in order. + var searchPaths = new List(); + searchPaths.Add(get_current_directory()); + searchPaths.Add(get_directory_name(get_current_assembly_path())); + searchPaths.AddRange(Environment.GetEnvironmentVariable(ApplicationParameters.Environment.Path).to_string().Split(new[] { get_path_separator() }, StringSplitOptions.RemoveEmptyEntries)); + + foreach (var path in searchPaths.or_empty_list_if_null()) + { + foreach (var extension in extensions.or_empty_list_if_null()) + { + var possiblePath = combine_paths(path, "{0}{1}".format_with(executableName, extension.to_lower())); + if (file_exists(possiblePath)) return possiblePath; + } + } + + // If not found, return the same as passed in - it may work, + // but possibly not. + return executableName; + } + + public string get_current_assembly_path() + { + return Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty); + } + #endregion #region File @@ -127,7 +195,7 @@ public FileInfo get_file_info_for(string filePath) return new FileInfo(filePath); } - public DateTime get_file_modified_date(string filePath) + public System.DateTime get_file_modified_date(string filePath) { return new FileInfo(filePath).LastWriteTime; } diff --git a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs index 6a5a3311c4..cebd04fc36 100644 --- a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs @@ -53,6 +53,20 @@ public interface IFileSystem /// char get_path_directory_separator_char(); + /// + /// Gets the path to an executable based on looking in current directory, next to the running process, then among the derivatives of Path and Pathext variables + /// + /// Name of the executable. + /// Based loosely on http://stackoverflow.com/a/5471032/18475 + /// + string get_executable_path(string executableName); + + /// + /// Gets the location of the executing assembly + /// + /// The path to the executing assembly + string get_current_assembly_path(); + #endregion #region File @@ -253,7 +267,7 @@ public interface IFileSystem /// /// Gets the current working directory of the application. /// - /// + /// The path to the directory string get_current_directory(); ///