├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── Reference └── CAB Format Notes.txt ├── appveyor.yml ├── art ├── lessmsiicon.png ├── lessmsiicon.pxm ├── lessmsiicon32x32.png └── lessmsiicon32x32.pxm ├── contrib └── python │ ├── LessMsi.py │ ├── setup.cfg │ └── setup.py ├── lib ├── wix.dll └── wixcab.dll ├── misc ├── CompoundDocuments │ ├── CompoundDocumentViewer11.zip │ └── SSView.zip ├── screenshot-explorerintegration.png ├── screenshot-filestab.png ├── screenshot-preferences.png ├── screenshot-summarytab.png └── screenshot-tabletab.png ├── release.config.js └── src ├── .build ├── MSBuild.Community.Tasks.dll ├── MSBuild.Community.Tasks.targets ├── VERIFICATION.txt ├── chocolateyInstall.ps1 ├── lessmsi.msbuild ├── lessmsi.nuspec ├── nuget-restore.bat ├── semantic-release-prepare.cmd ├── semantic-release-publish.cmd └── semantic-release-verify.cmd ├── .nuget ├── NuGet.Config ├── NuGet.exe ├── NuGet.mono.targets ├── NuGet.targets └── packages.config ├── CommonAssemblyInfo.cs ├── ExplorerShortcutHelper ├── ExplorerShortcutHelper.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── RegistryTools.cs ├── app.config └── app.manifest ├── LICENSE ├── LessMsi.Cli ├── App.config ├── ExtractCommand.cs ├── LessMsi.Cli.csproj ├── LessMsiCommand.cs ├── ListTableCommand.cs ├── OpenGuiCommand.cs ├── Options.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── ShowHelpCommand.cs ├── ShowVersionCommand.cs └── packages.config ├── LessMsi.Core ├── LessMsi.Core.csproj ├── Msi │ ├── ColumnInfo.cs │ ├── ExternalCabNotFoundException.cs │ ├── ExtractionMode.cs │ ├── MsiDatabase.cs │ ├── MsiDirectory.cs │ ├── MsiFile.cs │ ├── TableWrapper.cs │ ├── ViewWrapper.cs │ └── Wixtracts.cs ├── OleStorage │ ├── NativeMethods.cs │ ├── OleStorageFile.cs │ └── README.md ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── LessMsi.Gui ├── AboutBox.Designer.cs ├── AboutBox.cs ├── AboutBox.resx ├── ChangeLanguageForm.Designer.cs ├── ChangeLanguageForm.cs ├── ChangeLanguageForm.resx ├── Extensions │ ├── DataGridViewExtensions.cs │ ├── MsiNativeMethods.cs │ └── SummaryInformationExtensions.cs ├── ExtractionProgressDialog.cs ├── IMainFormView.cs ├── LessMsi.Gui.csproj ├── MainForm.cs ├── MainForm.resx ├── MainFormPresenter.cs ├── Model │ ├── CabContainedFileView.cs │ ├── MsiFileItemView.cs │ ├── MsiPropertyInfo.cs │ └── StreamInfoView.cs ├── MruMenuStripManager.cs ├── PreferencesForm.Designer.cs ├── PreferencesForm.cs ├── PreferencesForm.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Resources │ ├── Languages │ │ ├── Strings.Designer.cs │ │ ├── Strings.de.resx │ │ ├── Strings.fr.resx │ │ ├── Strings.it.resx │ │ ├── Strings.pt.resx │ │ └── Strings.resx │ └── LessmsiIcon.ico ├── Windows.Forms │ ├── DisposableCursor.cs │ ├── ElevationButton.cs │ ├── ElevationNativeMethods.cs │ ├── ErrorDialog.Designer.cs │ ├── ErrorDialog.cs │ ├── ErrorDialog.resx │ ├── SearchPanel.Designer.cs │ ├── SearchPanel.cs │ ├── SearchPanel.resx │ ├── SortableBindingList.cs │ └── WinFormsHelper.cs ├── aboutbox.rtf ├── app.config └── packages.config ├── LessMsi.sln ├── Lessmsi.Tests ├── CommandLineExtractTests.cs ├── CompareEntriesResult.cs ├── FileEntry.cs ├── FileEntryGraph.cs ├── GUITests.cs ├── LessMsi.Tests.csproj ├── MiscTests.cs ├── MiscTestsNUnit.cs ├── MspTests.cs ├── OleStorageTests.cs ├── Properties │ └── AssemblyInfo.cs ├── TestBase.cs ├── TestFiles │ ├── ExpectedOutput │ │ ├── Extract1Arg.expected.csv │ │ ├── Extract1ArgRelativePath.expected.csv │ │ ├── Extract2Args.expected.csv │ │ ├── Extract2ArgsRelativePath.expected.csv │ │ ├── Extract3Args.expected.csv │ │ ├── Extract3ArgsRelativePath.expected.csv │ │ ├── ExtractCompatibility1Arg.expected.csv │ │ ├── ExtractCompatibility2Args.expected.csv │ │ ├── ExtractOnlySomeFiles.msi.expected.csv │ │ ├── FlatOverwriteExtract1Arg.expected.csv │ │ ├── FlatOverwriteExtract2Args.expected.csv │ │ ├── FlatOverwriteExtract3Args.expected.csv │ │ ├── FlatRenameExtract1Arg.expected.csv │ │ ├── FlatRenameExtract2Args.expected.csv │ │ ├── FlatRenameExtract3Args.expected.csv │ │ ├── IviNetSharedComponents32_Fx20_1.3.0.msi.expected.csv │ │ ├── NUnit-2.5.2.9222.msi.expected.csv │ │ ├── OverwriteExtract1Arg.expected.csv │ │ ├── OverwriteExtract2Args.expected.csv │ │ ├── OverwriteExtract3Args.expected.csv │ │ ├── Path With Spaces │ │ │ └── spaces example.msi.expected.csv │ │ ├── X86 Debuggers And Tools-x86_en-us.msi.expected.csv │ │ ├── msi_with_external_cab.msi.expected.csv │ │ ├── putty-0.68-installer.msi.expected.csv │ │ ├── python-2.7.3.msi.expected.csv │ │ └── vcredist.msi.expected.csv │ ├── MsiInput │ │ ├── AppleMobileDeviceSupport64.msi │ │ ├── ExtractOnlySomeFiles.msi │ │ ├── IviNetSharedComponents32_Fx20_1.3.0.msi │ │ ├── NUnit-2.5.2.9222.msi │ │ ├── Path With Spaces │ │ │ └── spaces example.msi │ │ ├── SQL2008_AS.msp │ │ ├── Slik-Subversion-1.6.6-x64.msi │ │ ├── VBRuntime.msi │ │ ├── WPF2_32.msp │ │ ├── X86 Debuggers And Tools-x86_en-us.msi │ │ ├── long-directory-name │ │ │ └── very │ │ │ │ └── unusually │ │ │ │ └── long │ │ │ │ └── directory │ │ │ │ └── name │ │ │ │ └── with │ │ │ │ └── cream │ │ │ │ └── sugar │ │ │ │ └── and │ │ │ │ └── chocolate │ │ │ │ └── topping │ │ │ │ └── python-2.7.3.msi │ │ ├── msi_with_external_cab.cab │ │ ├── msi_with_external_cab.msi │ │ ├── msxml5.msp │ │ ├── putty-0.68-installer.msi │ │ ├── python-2.7.3.msi │ │ ├── vcredis1.cab │ │ └── vcredist.msi │ ├── create_msi_with_external_cab.cmd │ └── create_msi_with_external_cab.wxs └── packages.config ├── build.bat ├── build.sh ├── clean.bat ├── packages └── repositories.config └── test.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | # We use this to keep .sh (like build.sh) with lf only line endings to keep bash from puking when it's edited on someone's windows box. See http://git-scm.com/docs/gitattributes#_checking-out_and_checking-in 2 | 3 | *.sh eol=lf -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to LessMSI. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | # Questions, Suggestions, Bugs 8 | Feel free to submit issues at https://github.com/activescott/lessmsi/issues to report bugs, request enhancements or just ask a question. 9 | 10 | 11 | # Your First Code Contribution 12 | 13 | Unsure where to begin contributing? You can start by looking through these `beginner` and `help-wanted` issues: 14 | 15 | * [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two. 16 | * [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. 17 | 18 | Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. 19 | 20 | ## Pull Requests ## 21 | To get your changes into a Lessmsi release, please fork the repository, commit your changes to your fork, then submit a pull request. If you're not super confident about pull requests see Github's great documentation about how to do so at https://help.github.com/articles/about-pull-requests/ 22 | 23 | 24 | Based off of https://github.com/atom/atom/blob/a7ad7478b7cd386ff377615841fcd91fa241640b/CONTRIBUTING.md 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: activescott/lessmsi # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://tip4commit.com/github/activescott/lessmsi'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 3 | 4 | Please fill out the following checklist: 5 | 6 | - [ ] Added tests for any bug fixes that changed existing code 7 | - [ ] Added tests for any new behavior 8 | - [ ] All unit tests are passing (please make sure all tests are passing for your branch/PR at https://ci.appveyor.com/project/activescott/lessmsi/history ) 9 | 10 | 11 | If you need any help at all, feel free to submit the PR and @-mention activescott and I'll be happy to assist! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | [Bb]in/ 3 | [Oo]bj/ 4 | *.suo 5 | *.user 6 | *.csproj.user 7 | *.dtbcache.json 8 | *.userprefs 9 | *.wixpdb 10 | *.wixobj 11 | # VIM 12 | *~ 13 | *.swp 14 | /src/.deploy 15 | _Resharper*/ 16 | 17 | # Nuget: http://docs.nuget.org/consume/package-restore#omitting-packages-from-source-control 18 | ## Ignore NuGet Packages 19 | *.nupkg 20 | ## Ignore the packages folder 21 | **/packages/* 22 | ## except build/, which is used as an MSBuild target. 23 | !**/packages/build/ 24 | ## Uncomment if necessary however generally it will be regenerated when needed 25 | !**/packages/repositories.config 26 | 27 | # Misc 28 | src/TestResult.xml 29 | src/.build/nuget.exe 30 | src/msbuild.log 31 | src/.vs/ 32 | src/.temp/ 33 | .vs/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at lessmsi@willeke.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Reference/CAB Format Notes.txt: -------------------------------------------------------------------------------- 1 | CAB Format Notes 2 | http://www.cabextract.org.uk/wince_cab_format/ 3 | http://msdn.microsoft.com/en-us/library/bb267310(VS.85).aspx 4 | http://www.ddj.com/184410186;jsessionid=30Q2P0IQ3EA4XQE1GHPSKH4ATMY32JVN?_requestid=420442 5 | http://msdn.microsoft.com/en-us/library/aa367841%28VS.85%29.aspx 6 | http://en.wikipedia.org/wiki/Cabinet_%28file_format%29 7 | 8 | InstallShield has their own Proprietary CAB format. SynCE Project has a decompressor for it at: http://sourceforge.net/projects/synce/files/Unshield/0.6/ 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # see http://www.appveyor.com/docs/appveyor-yml 2 | # environment info: https://www.appveyor.com/docs/windows-images-software/#visual-studio-2015 3 | image: Visual Studio 2022 4 | 5 | environment: 6 | # from https://github.com/settings/tokens encrypted at https://ci.appveyor.com/tools/encrypt 7 | GH_TOKEN: 8 | secure: MJhg1HrP+chjNOM6GAdrQzMWMTHNwmzD57kWT7mhWwnOOJiag1EZHHgVtrTUmGJ1syJncgmKTVofdOND+9QmqbpbMji6V0eN9EPXN6t+Os78H+wuCj58GLWjel0x+nM9 9 | CHOCO_KEY: 10 | secure: /Ie5xuB5GTDwElbSN0V+mCyYtYNhfQJRjdoWFUsNsVJW9bq32LNzSlI1cn4OUxIu 11 | 12 | #---------------------------------# 13 | # build configuration # 14 | #---------------------------------# 15 | build_script: 16 | - cmd: msbuild .\src\.build\lessmsi.msbuild /p:TheVersion=%APPVEYOR_BUILD_VERSION% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" 17 | 18 | #---------------------------------# 19 | # tests configuration # 20 | #---------------------------------# 21 | test: 22 | assemblies: 23 | - .\src\.deploy\LessMsi.Tests.dll 24 | 25 | artifacts: 26 | - path: 'src\.deploy\chocolateypackage\*.nupkg' 27 | - path: 'src\.deploy\*.zip' 28 | 29 | # NOTE: Deployments, before_deploy and after_deploy scripts are disabled by default on Pull Requests. 30 | # https://www.appveyor.com/docs/deployment/#pull-requests 31 | deploy_script: 32 | # install (select) node 10 per semantic-release requirements: https://www.appveyor.com/docs/lang/nodejs-iojs/#selecting-nodejs-or-iojs-version 33 | - ps: Install-Product node 10 34 | # install semantic-release and reequirement plugins: 35 | - cmd: npm i semantic-release@17 @semantic-release/exec@5 36 | # run semantic-release with config: 37 | - cmd: node_modules\.bin\semantic-release 38 | -------------------------------------------------------------------------------- /art/lessmsiicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/art/lessmsiicon.png -------------------------------------------------------------------------------- /art/lessmsiicon.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/art/lessmsiicon.pxm -------------------------------------------------------------------------------- /art/lessmsiicon32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/art/lessmsiicon32x32.png -------------------------------------------------------------------------------- /art/lessmsiicon32x32.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/art/lessmsiicon32x32.pxm -------------------------------------------------------------------------------- /contrib/python/LessMsi.py: -------------------------------------------------------------------------------- 1 | __all__=["doExtraction", "ExtrProgress"] 2 | import typing 3 | import clr 4 | clr.AddReference("LessMsi.core") 5 | clr.AddReference("LessIO") 6 | import LessIO 7 | from LessMsi.Msi import Wixtracts 8 | from pathlib import Path 9 | 10 | __license__="MIT" 11 | __copyright__=r""" 12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 19 | 20 | Authors: 21 | Scott Willeke (scott@willeke.com) 22 | """ 23 | 24 | class ExtrProgress: 25 | """Represents progress of unpacking""" 26 | __slots__=("current", "total", "fileName") 27 | def __init__(self, current:int, total:int, fileName:Path): 28 | self.current=current 29 | self.total=total 30 | self.fileName=fileName 31 | def __repr__(self): 32 | return self.__class__.__name__+"("+", ".join((str(self.current), str(self.total), str(self.fileName)))+")" 33 | 34 | from time import sleep 35 | def doExtraction(msiFileName:Path, outDirName:Path="", filesToExtract:typing.Iterable[Path]=None, progressCallback=None): 36 | """Extracts files from a .msi. 37 | See https://github.com/activescott/lessmsi/blob/master/src/LessMsi.Cli/Program.cs#L104 for more info 38 | """ 39 | 40 | msiFileName=str(Path(msiFileName).absolute()) 41 | outDirName=str(Path(outDirName).absolute()) 42 | if filesToExtract: 43 | filesToExtract=(str(Path(outDirName).absolute()) for f in filesToExtract) 44 | msiFile = LessIO.Path(msiFileName) 45 | if progressCallback: 46 | def cb(progress:Wixtracts.ExtractionProgress): 47 | #progress.Activity 48 | return progressCallback(ExtrProgress(progress.FilesExtractedSoFar, progress.TotalFileCount, Path(progress.CurrentFileName))) 49 | cb=clr.System.AsyncCallback(cb) 50 | else: 51 | cb=None 52 | Wixtracts.ExtractFiles(msiFile, outDirName, filesToExtract, cb) 53 | 54 | try: 55 | from tqdm import tqdm 56 | def doExtractionWithTqdmProgressBar(msiFileName:Path, outDirName:Path="", filesToExtract:typing.Iterable[Path]=None, progressCallback=None): 57 | """Extracts files from a .msi showing a tqdm-based progressbar.""" 58 | prev=0 59 | with tqdm(unit="file") as pb: 60 | def cb(progr:ExtrProgress): 61 | nonlocal pb, prev 62 | pb.desc=str(progr.fileName) 63 | delta=progr.current-prev 64 | prev=progr.current 65 | pb.total=progr.total 66 | pb.update(delta) 67 | if progressCallback: 68 | return progressCallback(progr) 69 | doExtraction(msiFileName, outDirName=outDirName, filesToExtract=filesToExtract, progressCallback=cb) 70 | __all__.append(doExtractionWithTqdmProgressBar.__name__) 71 | except ImportError: 72 | pass 73 | -------------------------------------------------------------------------------- /contrib/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = LessMsi 3 | #version = 0.1 4 | author = KOLANICH 5 | url = https://github.com/KOLANICH/lessmsi/tree/python/contrib/python 6 | description = A python wrapper for extraction of MSIs with lessmsi 7 | keywords = msi, cab, extract, python 8 | license = MIT 9 | classifiers = 10 | Programming Language :: Python 11 | Programming Language :: Python :: 3 12 | Development Status :: 4 - Beta 13 | Environment :: Other Environment 14 | Intended Audience :: Developers 15 | License :: OSI Approved 16 | License :: OSI Approved :: MIT License 17 | Operating System :: OS Independent 18 | Topic :: Software Development :: Libraries :: Python Modules 19 | Topic :: Science 20 | 21 | [options] 22 | python_requires = >=3.4 23 | zip_safe = True 24 | py_modules = LessMsi 25 | setup_requires = setuptools_scm; 26 | install_requires = 27 | pythonnet 28 | 29 | -------------------------------------------------------------------------------- /contrib/python/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from pathlib import Path 3 | from setuptools import setup 4 | from setuptools.config import read_configuration 5 | 6 | cfg = read_configuration(Path(__file__).parent / 'setup.cfg') 7 | #print(cfg) 8 | cfg["options"].update(cfg["metadata"]) 9 | cfg=cfg["options"] 10 | setup(use_scm_version = True, **cfg) 11 | -------------------------------------------------------------------------------- /lib/wix.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/lib/wix.dll -------------------------------------------------------------------------------- /lib/wixcab.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/lib/wixcab.dll -------------------------------------------------------------------------------- /misc/CompoundDocuments/CompoundDocumentViewer11.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/CompoundDocuments/CompoundDocumentViewer11.zip -------------------------------------------------------------------------------- /misc/CompoundDocuments/SSView.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/CompoundDocuments/SSView.zip -------------------------------------------------------------------------------- /misc/screenshot-explorerintegration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/screenshot-explorerintegration.png -------------------------------------------------------------------------------- /misc/screenshot-filestab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/screenshot-filestab.png -------------------------------------------------------------------------------- /misc/screenshot-preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/screenshot-preferences.png -------------------------------------------------------------------------------- /misc/screenshot-summarytab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/screenshot-summarytab.png -------------------------------------------------------------------------------- /misc/screenshot-tabletab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/misc/screenshot-tabletab.png -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: [ 3 | "master" 4 | ], 5 | plugins: [ 6 | "@semantic-release/commit-analyzer", 7 | ["@semantic-release/release-notes-generator", { 8 | writerOpts: { 9 | footerPartial: ` 10 | {{#if noteGroups}} 11 | {{#each noteGroups}} 12 | 13 | ### {{title}} 14 | 15 | {{#each notes}} 16 | * {{text}} 17 | {{/each}} 18 | {{/each}} 19 | {{/if}} 20 | 21 | You can install or upgrade by extracting all files from the zip attached to this release into a single directory or via [Chocolatey](https://chocolatey.org/packages/lessmsi). 22 | ` 23 | } 24 | }], 25 | // github config docs: https://github.com/semantic-release/github 26 | ["@semantic-release/github", { 27 | "assets": [ 28 | {"path": "src/.deploy/chocolateypackage/*.nupkg", "label": "Chocolatey Package"}, 29 | {"path": "src/.deploy/*.zip", "label": "Zip of lessmsi application binaries"} 30 | ] 31 | }], 32 | ["@semantic-release/exec", { 33 | "verifyConditionsCmd": "src\\.build\\semantic-release-verify.cmd", 34 | "prepareCmd": "src\\.build\\semantic-release-prepare.cmd ${nextRelease.version}", 35 | "publishCmd": "src\\.build\\semantic-release-publish.cmd ${nextRelease.version}", 36 | }], 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /src/.build/MSBuild.Community.Tasks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/.build/MSBuild.Community.Tasks.dll -------------------------------------------------------------------------------- /src/.build/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | This package is published by the author and this file is here only because chocolatey requires it as described at https://github.com/chocolatey/package-validator/wiki/VerificationFileMissing -------------------------------------------------------------------------------- /src/.build/chocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | 2 | try { 3 | 4 | $theFile = Join-Path $(Split-Path -parent $MyInvocation.MyCommand.Definition) "AddWindowsExplorerShortcut.exe.ignore" 5 | #Write-Host "Creating " $theFile 6 | New-Item $theFile -type file 7 | 8 | $zipFile = Join-Path $(Split-Path -parent $MyInvocation.MyCommand.Definition) "__ZIP_FILE__" 9 | 10 | $toolsDir = (Split-Path -parent $MyInvocation.MyCommand.Definition) 11 | Get-ChocolateyUnzip -FileFullPath "$zipFile" -Destination $toolsDir 12 | 13 | } catch { 14 | throw $_.Exception 15 | } 16 | -------------------------------------------------------------------------------- /src/.build/lessmsi.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lessmsi 5 | LessMSI 6 | 1.0.* 7 | Scott Willeke 8 | Scott Willeke 9 | LessMSI - Easily extract the contents of an MSI 10 | LessMSI is a utility with a graphical user interface and a command line interface that can be used to view and extract the contents of an MSI file. For Windows. Usage on the command line: lessmsi x msiFileName [outDir] 11 | 12 | msi extract extraction install 13 | https://lessmsi.activescott.com 14 | https://www.opensource.org/licenses/mit-license.php 15 | false 16 | https://github.com/activescott/lessmsi 17 | https://github.com/activescott/lessmsi 18 | https://github.com/activescott/lessmsi/wiki 19 | https://github.com/activescott/lessmsi/releases 20 | https://github.com/activescott/lessmsi/issues 21 | https://lessmsi.activescott.com/images/lessmsiicon32x32.png 22 | https://github.com/activescott/lessmsi/issues 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/.build/nuget-restore.bat: -------------------------------------------------------------------------------- 1 | REM: Deos nothing: nuget.exe restore -Verbosity Detailed -NonInteractive -OutputDirectory c:\src\lessmsi\src\packages\ -Source "https://api.nuget.org/v3/index.json" c:\src\lessmsi\src\packages\repositories.config 2 | REM: Error, seems to ignore SolutionDirectory: nuget.exe restore -Verbosity Detailed -NonInteractive -OutputDirectory c:\src\lessmsi\src\packages\ -Source "https://api.nuget.org/v3/index.json" -SolutionDirectory c:\src\lessmsi\src 3 | nuget.exe restore -Verbosity Detailed -NonInteractive -OutputDirectory c:\src\lessmsi\src\packages\ -Source "https://api.nuget.org/v3/index.json" c:\src\lessmsi\src\LessMsi.sln 4 | -------------------------------------------------------------------------------- /src/.build/semantic-release-prepare.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM This script called by semantic-release to "prepare" the build. 4 | REM Since semantic-release determines the version number based on commits, we rebuild it with the new version number: 5 | 6 | IF [%1]==[] ( 7 | ECHO ERROR: Must supply build version as first parameter to this bat file! 1>&2 8 | EXIT 1 9 | ) 10 | set _BUILD_VERSION=%1 11 | 12 | ECHO Running msbuild... 13 | 14 | msbuild .\src\.build\lessmsi.msbuild /p:TheVersion=%_BUILD_VERSION% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" 15 | 16 | REM NOTE: ECHO does not clear/set errorlevel https://ss64.com/nt/errorlevel.htmls 17 | ECHO Running msbuild complete. 18 | 19 | EXIT %ERRORLEVEL% 20 | -------------------------------------------------------------------------------- /src/.build/semantic-release-publish.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM note: Nodejs configured and semantic-release insalled in appveyor.yml 4 | REM This script is called /by/ semantic-release to publish to chocolatey 5 | 6 | set THIS_DIR=%~dp0 7 | 8 | IF [%1]==[] ( 9 | ECHO ERROR: Must supply build version as first parameter to this bat file! 1>&2 10 | EXIT 1 11 | ) 12 | set _BUILD_VERSION=%1 13 | 14 | ECHO Running choco push... 15 | 16 | choco push --source https://push.chocolatey.org/ --api-key=%CHOCO_KEY% "%THIS_DIR%..\.deploy\chocolateypackage\lessmsi.%_BUILD_VERSION%.nupkg" 17 | 18 | REM NOTE: ECHO does not clear/set errorlevel https://ss64.com/nt/errorlevel.htmls 19 | ECHO Running choco push complete. Errorlevel was %ERRORLEVEL% 20 | 21 | EXIT %ERRORLEVEL% 22 | -------------------------------------------------------------------------------- /src/.build/semantic-release-verify.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | IF DEFINED CHOCO_KEY ( 4 | echo "CHOCO_KEY is defined" 5 | EXIT 0 6 | ) ELSE ( 7 | echo "CHOCO_KEY is NOT defined" 1>&2 8 | EXIT 1 9 | ) 10 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/.nuget/NuGet.exe -------------------------------------------------------------------------------- /src/.nuget/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/CommonAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System.Reflection; 26 | using System.Runtime.InteropServices; 27 | 28 | [assembly: AssemblyVersion("1.0.10.*")] 29 | [assembly: AssemblyCopyright("Copyright Scott Willeke © 2004-2021")] 30 | 31 | // Setting ComVisible to false makes the types in this assembly not visible 32 | // to COM components. If you need to access a type in this assembly from 33 | // COM, set the ComVisible attribute to true on that type. 34 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /src/ExplorerShortcutHelper/ExplorerShortcutHelper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 9.0.30729 7 | 2.0 8 | {13F99803-59EF-4DEA-BE1C-68561A427B08} 9 | Exe 10 | Properties 11 | Willeke.Scott.ExplorerShortcutHelper 12 | AddWindowsExplorerShortcut 13 | v4.8 14 | 512 15 | 16 | 17 | app.manifest 18 | 19 | 20 | 3.5 21 | 22 | false 23 | publish\ 24 | true 25 | Disk 26 | false 27 | Foreground 28 | 7 29 | Days 30 | false 31 | false 32 | true 33 | 0 34 | 1.0.0.%2a 35 | false 36 | true 37 | 38 | 39 | 40 | true 41 | bin\Debug\ 42 | DEBUG;TRACE 43 | full 44 | x86 45 | prompt 46 | AllRules.ruleset 47 | false 48 | 49 | 50 | bin\Release\ 51 | TRACE 52 | true 53 | pdbonly 54 | x86 55 | prompt 56 | AllRules.ruleset 57 | false 58 | 59 | 60 | 61 | Properties\CommonAssemblyInfo.cs 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | False 74 | .NET Framework 3.5 SP1 Client Profile 75 | false 76 | 77 | 78 | False 79 | .NET Framework 3.5 SP1 80 | true 81 | 82 | 83 | False 84 | Microsoft Visual Basic PowerPacks 10.0 85 | true 86 | 87 | 88 | False 89 | Windows Installer 3.1 90 | true 91 | 92 | 93 | 94 | 101 | -------------------------------------------------------------------------------- /src/ExplorerShortcutHelper/Program.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Globalization; 27 | 28 | namespace Willeke.Scott.ExplorerShortcutHelper 29 | { 30 | class Program 31 | { 32 | static int Main(string[] args) 33 | { 34 | bool? isAddingKey = null; 35 | if (args != null && args.Length > 0) 36 | { 37 | var addOrRemoveString = args[0]; // add or remove 38 | if (!string.IsNullOrEmpty(addOrRemoveString)) 39 | { 40 | if ("add".Equals(addOrRemoveString, StringComparison.InvariantCulture)) 41 | isAddingKey = true; 42 | else if ("remove".Equals(addOrRemoveString, StringComparison.InvariantCulture)) 43 | isAddingKey = false; 44 | } 45 | } 46 | 47 | if (!isAddingKey.HasValue) 48 | return CommandLineError("Invalid argument. Expected 'add' or 'remove'."); 49 | 50 | 51 | if (args.Length < 2) 52 | return CommandLineError("Invalid argument. Expected a unique command name."); 53 | 54 | string commandName = args[1]; 55 | 56 | if (args.Length < 3) 57 | return CommandLineError("Invalid argument. Expected a file class."); 58 | 59 | var fileClass = args[2]; //e.g. "Msi.Package"; 60 | 61 | if (isAddingKey.Value) 62 | { // if we're adding a key we're expecting more arguments: 63 | 64 | if (args.Length < 3) 65 | return CommandLineError("Invalid argument. Expected caption."); 66 | 67 | var contextMenuCaption = args[3]; // e.g. "&Extract Files"; 68 | var shellCommandToExecute = args[4]; //e.g. '\"' + GetExePath() + "\" /x \"%1\" \"%1_extracted\""; 69 | 70 | var message = string.Format(CultureInfo.InvariantCulture, 71 | "Adding a shortcut for '{0}' files that will execute the following command: '{1}'...", 72 | fileClass, shellCommandToExecute); 73 | 74 | Console.WriteLine(message); 75 | try 76 | { 77 | RegistryTool.RegisterFileVerb(commandName, fileClass, contextMenuCaption, shellCommandToExecute); 78 | } 79 | catch (Exception oops) 80 | { 81 | Console.WriteLine("Error adding shortcut menu: " + oops.ToString()); 82 | return -1; 83 | } 84 | Console.WriteLine("Shortcut added."); 85 | } 86 | else 87 | { 88 | Console.WriteLine("Removing shortcut..."); 89 | try 90 | { 91 | RegistryTool.UnRegisterFileVerb(commandName, fileClass); 92 | } 93 | catch (Exception oops) 94 | { 95 | Console.WriteLine("Error removing shortcut menu: " + oops.ToString()); 96 | return -1; 97 | } 98 | Console.WriteLine("Shortcut removed."); 99 | } 100 | return 0; 101 | } 102 | 103 | private static int CommandLineError(string errorMessage) 104 | { 105 | Console.WriteLine("Adds or removes a shortcut menu item for a specific type of file in Windows Explorer."); 106 | Console.WriteLine(); 107 | Console.WriteLine("Usage: AddWindowsExplorerShortcut add|remove commandName fileClass [caption shellCommand]"); 108 | Console.WriteLine(); 109 | Console.WriteLine(errorMessage); 110 | return 100; 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/ExplorerShortcutHelper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | 26 | using System.Reflection; 27 | 28 | // General Information about an assembly is controlled through the following 29 | // set of attributes. Change these attribute values to modify the information 30 | // associated with an assembly. 31 | [assembly: AssemblyTitle("Explorer Shortcut Helper for Lessmsi")] 32 | [assembly: AssemblyProduct("Explorer Shortcut Helper for Lessmsi")] 33 | 34 | -------------------------------------------------------------------------------- /src/ExplorerShortcutHelper/RegistryTools.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Security; 27 | using Microsoft.Win32; 28 | 29 | namespace Willeke.Scott.ExplorerShortcutHelper 30 | { 31 | internal class RegistryTool 32 | { 33 | /// 34 | /// Registers a verb for the specified windows explorer/shell file class. 35 | /// 36 | /// A unique name for your verb/command. It should be just different from any other applications command. 37 | /// The file class for the file to register the verb for (see http://msdn.microsoft.com/en-us/library/bb776870(VS.85).aspx ). 38 | /// The caption for the verb. 39 | /// The command to execute (as it would be executed in ShellExecute). 40 | public static void RegisterFileVerb(string commandName, string fileClassName, string contextMenuCaption, string shellCommandToExecute) 41 | { 42 | string regRoot = fileClassName + @"\shell\" + commandName; 43 | var extractKey = OpenRegistryKeyWithAddedErrorInfo(regRoot); 44 | extractKey.SetValue("", contextMenuCaption); 45 | 46 | var commandKeyValue = OpenRegistryKeyWithAddedErrorInfo(regRoot + "\\command"); 47 | var existingValue = commandKeyValue.GetValue("") as string; 48 | if (existingValue != null && existingValue.StartsWith(shellCommandToExecute)) 49 | return; 50 | commandKeyValue.SetValue("", shellCommandToExecute); 51 | } 52 | 53 | /// 54 | /// Deletes/unregisteres a verb previously registered with . 55 | /// 56 | /// See . 57 | /// See . 58 | public static void UnRegisterFileVerb(string commandName, string fileClassName) 59 | { 60 | var extractKey = OpenRegistryKeyWithAddedErrorInfo(fileClassName + @"\shell\"); 61 | extractKey.DeleteSubKeyTree(commandName); 62 | } 63 | 64 | /// 65 | /// Opens a key with write permission and adds error information if it fails. 66 | /// 67 | private static RegistryKey OpenRegistryKeyWithAddedErrorInfo(string registryKeyName) 68 | { 69 | RegistryKey extractKey; 70 | try 71 | { 72 | extractKey = Registry.ClassesRoot.CreateSubKey(registryKeyName, RegistryKeyPermissionCheck.ReadWriteSubTree); 73 | } 74 | catch (SecurityException eSecurity) 75 | { 76 | throw new SecurityException("You do not have the permissions required to create or write the registry key '" + registryKeyName + "'.", eSecurity); 77 | } 78 | catch (Exception eCatchAll) 79 | { 80 | throw new Exception("Unable to open registry key '" + registryKeyName + "'.", eCatchAll); 81 | } 82 | return extractKey; 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/ExplorerShortcutHelper/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/ExplorerShortcutHelper/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/LICENSE: -------------------------------------------------------------------------------- 1 |  2 | From: http://www.opensource.org/licenses/mit-license.php 3 | 4 | LICENSE 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the 8 | // "Software"), to deal in the Software without restriction, including 9 | // without limitation the rights to use, copy, modify, merge, publish, 10 | // distribute, sublicense, and/or sell copies of the Software, and to 11 | // permit persons to whom the Software is furnished to do so, subject to 12 | // the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be 15 | // included in all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 26 | // 27 | // Authors: 28 | // Scott Willeke (scott@willeke.com) 29 | // -------------------------------------------------------------------------------- /src/LessMsi.Cli/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/LessMsi.Cli/ExtractCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using LessMsi.Msi; 5 | using NDesk.Options; 6 | 7 | namespace LessMsi.Cli 8 | { 9 | internal class ExtractCommand : LessMsiCommand 10 | { 11 | public override void Run(List allArgs) 12 | { 13 | var args = allArgs.Skip(1).ToList(); 14 | // "x msi_name [path_to_extract\] [file_names]+ 15 | if (args.Count < 1) 16 | throw new OptionException("Invalid argument. Extract command must at least specify the name of an msi file.", "x"); 17 | 18 | var i = 0; 19 | var msiFile = args[i++]; 20 | if (!File.Exists(msiFile)) 21 | throw new OptionException("Invalid argument. Specified msi file does not exist.", "x"); 22 | var filesToExtract = new List(); 23 | var extractDir = ""; 24 | if (i < args.Count) 25 | { 26 | if (extractDir == "" && (args[i].EndsWith("\\") || args[i].EndsWith("\""))) 27 | extractDir = args[i]; 28 | else 29 | filesToExtract.Add(args[i]); 30 | } 31 | while (++i < args.Count) 32 | filesToExtract.Add(args[i]); 33 | 34 | Program.DoExtraction(msiFile, extractDir.TrimEnd('\"'), filesToExtract, getExtractionMode(allArgs[0])); 35 | } 36 | 37 | private ExtractionMode getExtractionMode(string commandArgument) 38 | { 39 | commandArgument = commandArgument.ToLowerInvariant(); 40 | 41 | if (commandArgument == "xfo") 42 | { 43 | return ExtractionMode.OverwriteFlatExtraction; 44 | } 45 | 46 | if (commandArgument == "xfr") 47 | { 48 | return ExtractionMode.RenameFlatExtraction; 49 | } 50 | 51 | if (commandArgument == "xo") 52 | { 53 | return ExtractionMode.OverwriteExtraction; 54 | } 55 | 56 | return ExtractionMode.PreserveDirectoriesExtraction; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/LessMsi.Cli/LessMsi.Cli.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x86 7 | {7ED32F80-2B7B-4F90-ADE9-22B8F3935C63} 8 | Exe 9 | Properties 10 | LessMsi.Cli 11 | lessmsi 12 | v4.8 13 | 512 14 | 15 | 16 | ..\ 17 | true 18 | 19 | 20 | true 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | full 24 | x86 25 | prompt 26 | MinimumRecommendedRules.ruleset 27 | false 28 | 29 | 30 | bin\Release\ 31 | TRACE 32 | true 33 | pdbonly 34 | x86 35 | prompt 36 | MinimumRecommendedRules.ruleset 37 | false 38 | 39 | 40 | LessMsi.Cli.Program 41 | 42 | 43 | 44 | ..\packages\LessIO.1.0.34\lib\net40\LessIO.dll 45 | True 46 | 47 | 48 | 49 | 50 | ..\..\lib\wix.dll 51 | 52 | 53 | 54 | 55 | Properties\CommonAssemblyInfo.cs 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | {1fd916f2-1df6-42f6-9ad4-83565fbf8e5c} 74 | LessMsi.Core 75 | 76 | 77 | 78 | 79 | 80 | xcopy /y "$(SolutionDir)packages\libmspack4n.0.9.10\build\x86\mspack.dll" "$(TargetDir)" 81 | 82 | 89 | -------------------------------------------------------------------------------- /src/LessMsi.Cli/LessMsiCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LessMsi.Cli 4 | { 5 | internal abstract class LessMsiCommand 6 | { 7 | public abstract void Run(List args); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/LessMsi.Cli/ListTableCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | using LessMsi.Msi; 8 | using Microsoft.Tools.WindowsInstallerXml.Msi; 9 | using NDesk.Options; 10 | 11 | namespace LessMsi.Cli 12 | { 13 | internal class ListTableCommand : LessMsiCommand 14 | { 15 | public override void Run(List args) 16 | { 17 | /* examples: 18 | * lessmsi l -t Component c:\theinstall.msi 19 | * lessmsi l -t Property c:\theinstall.msi 20 | */ 21 | args = args.Skip(1).ToList(); 22 | var tableName = ""; 23 | var options = new OptionSet { 24 | { "t=", "Specifies the table to list.", t => tableName = t } 25 | }; 26 | var extra = options.Parse(args); 27 | if (extra.Count < 1) 28 | throw new OptionException("You must specify the msi file to list from.", "l"); 29 | if (string.IsNullOrEmpty(tableName)) 30 | throw new OptionException("You must specify the table name to list.", "t"); 31 | 32 | var csv = new StringBuilder(); 33 | Debug.Print("Opening msi file '{0}'.", extra[0]); 34 | using (var msidb = MsiDatabase.Create(new LessIO.Path(extra[0]))) 35 | { 36 | Debug.Print("Opening table '{0}'.", tableName); 37 | var query = string.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName); 38 | using (var view = new ViewWrapper(msidb.OpenExecuteView(query))) 39 | { 40 | for (var index = 0; index < view.Columns.Length; index++) 41 | { 42 | var col = view.Columns[index]; 43 | if (index > 0) 44 | csv.Append(','); 45 | csv.Append(col.Name); 46 | } 47 | csv.AppendLine(); 48 | foreach (var row in view.Records) 49 | { 50 | for (var colIndex = 0; colIndex < row.Length; colIndex++) 51 | { 52 | if (colIndex > 0) 53 | csv.Append(','); 54 | var val = Convert.ToString(row[colIndex], CultureInfo.InvariantCulture); 55 | var newLine = Environment.NewLine; 56 | string[] requireEscapeChars = { ",", newLine }; 57 | Array.ForEach(requireEscapeChars, s => { 58 | if (val.Contains(s)) 59 | val = "\"" + val + "\""; 60 | }); 61 | csv.Append(val); 62 | } 63 | csv.AppendLine(); 64 | } 65 | } 66 | } 67 | Console.Write(csv.ToString()); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/LessMsi.Cli/OpenGuiCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using NDesk.Options; 6 | 7 | namespace LessMsi.Cli 8 | { 9 | internal class OpenGuiCommand : LessMsiCommand 10 | { 11 | public override void Run(List args) 12 | { 13 | if (args.Count < 2) 14 | throw new OptionException("You must specify the name of the msi file to open when using the o command.", "o"); 15 | 16 | ShowGui(args); 17 | } 18 | 19 | public static void ShowGui(List args) 20 | { 21 | var guiExe = Path.Combine(AppPath, "lessmsi-gui.exe"); 22 | if (File.Exists(guiExe)) 23 | { 24 | var p = new Process(); 25 | p.StartInfo.FileName = guiExe; 26 | 27 | //should we wait for exit? 28 | if (args.Count > 0) 29 | { 30 | // We add double quotes to support paths with spaces, for ex: "E:\Downloads and Sofware\potato.msi". 31 | p.StartInfo.Arguments = string.Format("\"{0}\"", args[1]); 32 | p.Start(); 33 | } 34 | else 35 | p.Start(); 36 | } 37 | } 38 | 39 | private static string AppPath 40 | { 41 | get 42 | { 43 | var codeBase = new Uri(typeof(OpenGuiCommand).Assembly.CodeBase); 44 | var local = Path.GetDirectoryName(codeBase.LocalPath); 45 | return local; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/LessMsi.Cli/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | 26 | using System.Reflection; 27 | 28 | [assembly: AssemblyTitle("Less MSIérables (lessmsi) CLI interface")] 29 | [assembly: AssemblyProduct("Less MSIérables (lessmsi)")] 30 | -------------------------------------------------------------------------------- /src/LessMsi.Cli/ShowHelpCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LessMsi.Cli 5 | { 6 | internal class ShowHelpCommand : LessMsiCommand 7 | { 8 | public override void Run(List args) 9 | { 10 | ShowHelp(""); 11 | } 12 | 13 | public static void ShowHelp(string errorMessage) 14 | { 15 | string helpString = 16 | @"Usage: 17 | lessmsi [options] [] [file_names] 18 | 19 | Commands: 20 | x Extracts all or specified files from the specified msi_name. 21 | xfo Extracts all or specified files from the specified msi_name to the same folder while overwriting files with the same name. 22 | xfr Extracts all or specified files from the specified msi_name to the same folder while renaming files with the same name with a count suffix. 23 | l Lists the contents of the specified msi table as CSV to stdout. Table is 24 | specified with -t switch. Example: lessmsi l -t Component c:\foo.msi 25 | v Lists the value of the ProductVersion Property in the msi 26 | (typically this is the version of the MSI). 27 | o Opens the specified msi_name in the GUI. 28 | h Shows this help page. 29 | 30 | For more information see http://lessmsi.activescott.com 31 | "; 32 | if (!string.IsNullOrEmpty(errorMessage)) 33 | { 34 | helpString = "\r\nError: " + errorMessage + "\r\n\r\n" + helpString; 35 | } 36 | Console.WriteLine(helpString); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/LessMsi.Cli/ShowVersionCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using LessMsi.Msi; 5 | using Microsoft.Tools.WindowsInstallerXml.Msi; 6 | using NDesk.Options; 7 | 8 | namespace LessMsi.Cli 9 | { 10 | internal class ShowVersionCommand : LessMsiCommand 11 | { 12 | public override void Run(List args) 13 | { 14 | // args[0]=v, args[1]=filename.msi 15 | if (args.Count < 2) 16 | throw new OptionException("You must specify an msi filename.", "v"); 17 | var msiFileName = args[1]; 18 | using (var msidb = new Database(msiFileName, OpenDatabase.ReadOnly)) 19 | { 20 | const string tableName = "Property"; 21 | var query = string.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName); 22 | using (var view = new ViewWrapper(msidb.OpenExecuteView(query))) 23 | { 24 | foreach (var row in view.Records) 25 | { 26 | var property = (string)row[view.ColumnIndex("Property")]; 27 | var value = row[view.ColumnIndex("Value")]; 28 | if (string.Equals("ProductVersion", property, StringComparison.InvariantCultureIgnoreCase)) 29 | { 30 | Console.WriteLine(value); 31 | return; 32 | } 33 | } 34 | Console.WriteLine("Version not found!"); 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/LessMsi.Cli/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/LessMsi.Core/LessMsi.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x86 7 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C} 8 | Library 9 | Properties 10 | LessMsi 11 | lessmsi.core 12 | v4.8 13 | 512 14 | 15 | 16 | ..\ 17 | true 18 | 19 | 20 | 21 | 22 | true 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | full 26 | x86 27 | prompt 28 | MinimumRecommendedRules.ruleset 29 | false 30 | false 31 | 32 | 33 | bin\Release\ 34 | TRACE 35 | true 36 | pdbonly 37 | x86 38 | prompt 39 | MinimumRecommendedRules.ruleset 40 | false 41 | false 42 | 43 | 44 | 45 | ..\packages\LessIO.1.0.34\lib\net40\LessIO.dll 46 | True 47 | 48 | 49 | ..\packages\libmspack4n.0.9.10\lib\net40\libmspackn.dll 50 | True 51 | 52 | 53 | 54 | 55 | ..\..\lib\wix.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Properties\CommonAssemblyInfo.cs 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/LessMsi.Core/Msi/ColumnInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | 27 | namespace LessMsi.Msi 28 | { 29 | /// 30 | /// FYI: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/column_definition_format.asp 31 | /// 32 | public class ColumnInfo 33 | { 34 | public ColumnInfo(string name, string typeID) 35 | { 36 | this.Name = name; 37 | this.TypeID = typeID; 38 | } 39 | 40 | public string Name; 41 | 42 | /// 43 | /// s? String, variable length (?=1-255) 44 | /// s0 String, variable length 45 | /// i2 Short integer 46 | /// i4 Long integer 47 | /// v0 Binary Stream 48 | /// g? Temporary string (?=0-255) 49 | /// j? Temporary integer (?=0,1,2,4) 50 | /// O0 Temporary object 51 | /// An uppercase letter indicates that null values are allowed in the column. 52 | /// 53 | public string TypeID; 54 | 55 | 56 | public bool IsString 57 | { 58 | get 59 | { 60 | return 61 | TypeID[0] == 's' || TypeID[0] == 'S' 62 | || TypeID[0] == 'g' || TypeID[0] == 'G' 63 | || TypeID[0] == 'l' || TypeID[0] == 'L'; 64 | } 65 | } 66 | 67 | public bool IsInteger 68 | { 69 | get 70 | { 71 | return 72 | TypeID[0] == 'i' || TypeID[0] == 'I' 73 | || TypeID[0] == 'j' || TypeID[0] == 'J' 74 | ; 75 | } 76 | } 77 | 78 | public bool IsStream 79 | { 80 | get 81 | { 82 | return 83 | TypeID[0] == 'v' || TypeID[0] == 'V'; 84 | } 85 | } 86 | 87 | public bool IsObject 88 | { 89 | get { return string.Equals("O0", TypeID, StringComparison.InvariantCultureIgnoreCase); } 90 | } 91 | 92 | public int Size 93 | { 94 | get 95 | { 96 | return int.Parse(TypeID.Substring(1)); 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/LessMsi.Core/Msi/ExternalCabNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LessMsi.Msi 7 | { 8 | /// 9 | /// Thrown when the msi file indicates there should be an external .cab file (i.e. not embedded inside the MSI, but saved along side it) but that cab file does not exist. 10 | /// 11 | public class ExternalCabNotFoundException : Exception 12 | { 13 | public static ExternalCabNotFoundException CreateFromCabPath(string cabFileName, string expectedLocation) 14 | { 15 | var msg = string.Format("This msi file references a CAB file that is not embedded inside of the msi file itself. The CAB file is named {0} and was expected to be in the following folder: {1}", cabFileName, expectedLocation); 16 | return new ExternalCabNotFoundException(msg); 17 | } 18 | 19 | public ExternalCabNotFoundException() 20 | { 21 | } 22 | 23 | public ExternalCabNotFoundException(string message) 24 | : base(message) 25 | { 26 | } 27 | 28 | public ExternalCabNotFoundException(string message, Exception inner) 29 | : base(message, inner) 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LessMsi.Core/Msi/ExtractionMode.cs: -------------------------------------------------------------------------------- 1 | namespace LessMsi.Msi 2 | { 3 | public enum ExtractionMode 4 | { 5 | /// 6 | /// Default value indicating that a regular extraction should be performed. 7 | /// 8 | Default, 9 | /// 10 | /// Value indicating that a file extraction preserving directories should be performed. 11 | /// 12 | PreserveDirectoriesExtraction, 13 | /// 14 | /// Value indicating that a file extraction renaming identical files should be performed. 15 | /// 16 | RenameFlatExtraction, 17 | /// 18 | /// Value indicating that a file extraction overwriting identical files should be performed. 19 | /// 20 | OverwriteFlatExtraction, 21 | /// 22 | /// Value indicating that a file extraction overwriting identical files should be performed. 23 | /// While preserving the directories structures 24 | /// 25 | OverwriteExtraction 26 | } 27 | } -------------------------------------------------------------------------------- /src/LessMsi.Core/Msi/MsiDatabase.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2021 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using Microsoft.Tools.WindowsInstallerXml.Msi; 26 | 27 | namespace LessMsi.Msi 28 | { 29 | /// 30 | /// Helper class for opening an MSI Database or MSI Patch file 31 | /// 32 | public static class MsiDatabase 33 | { 34 | /// 35 | /// Documented flag, unlisted in Microsoft.Tools.WindowsInstallerXml.Msi.OpenDatabase 36 | /// 37 | const uint MSIDBOPEN_PATCHFILE = 32; 38 | 39 | /// 40 | /// Create a Database object from either an .msi or .mso file 41 | /// 42 | /// The path to the database or patch file 43 | /// 44 | public static Database Create(LessIO.Path msiDatabaseFilePath) 45 | { 46 | try 47 | { 48 | return new Database(msiDatabaseFilePath.PathString, OpenDatabase.ReadOnly); 49 | } 50 | catch (System.IO.IOException) 51 | { 52 | // retry as patchfile (.msp) 53 | return new Database(msiDatabaseFilePath.PathString, OpenDatabase.ReadOnly | (OpenDatabase)MSIDBOPEN_PATCHFILE); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/LessMsi.Core/Msi/TableWrapper.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Collections; 27 | using System.Collections.Specialized; 28 | using System.Diagnostics; 29 | using Microsoft.Tools.WindowsInstallerXml.Msi; 30 | 31 | namespace LessMsi.Msi 32 | { 33 | /// 34 | /// Represents a generic row in a table. 35 | /// 36 | public class TableRow 37 | { 38 | private readonly IDictionary _columns; 39 | 40 | private TableRow(IDictionary columns) 41 | { 42 | if (columns == null) 43 | throw new ArgumentNullException("columns"); 44 | _columns = columns; 45 | } 46 | 47 | public static TableRow[] GetRowsFromTable(Database msidb, string tableName) 48 | { 49 | if (!msidb.TableExists(tableName)) 50 | { 51 | Trace.WriteLine(string.Format("Table name '{0}' does not exist.", tableName)); 52 | return new TableRow[0]; 53 | } 54 | 55 | string query = string.Concat("SELECT * FROM `", tableName, "`"); 56 | using (ViewWrapper view = new ViewWrapper(msidb.OpenExecuteView(query))) 57 | { 58 | ArrayList /**/ rows = new ArrayList(view.Records.Count); 59 | 60 | ColumnInfo[] columns = view.Columns; 61 | foreach (object[] values in view.Records) 62 | { 63 | HybridDictionary valueCollection = new HybridDictionary(values.Length); 64 | for (int cIndex = 0; cIndex < columns.Length; cIndex++) 65 | { 66 | valueCollection[columns[cIndex].Name] = values[cIndex]; 67 | } 68 | rows.Add(new TableRow(valueCollection)); 69 | } 70 | return (TableRow[]) rows.ToArray(typeof(TableRow)); 71 | } 72 | } 73 | 74 | public string GetString(string columnName) 75 | { 76 | return Convert.ToString(GetValue(columnName)); 77 | } 78 | public object GetValue(string columnName) 79 | { 80 | return _columns[columnName]; 81 | } 82 | 83 | public Int32 GetInt32(string columnName) 84 | { 85 | return Convert.ToInt32(GetValue(columnName)); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/LessMsi.Core/Msi/ViewWrapper.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Collections.Generic; 27 | using System.Diagnostics; 28 | using Microsoft.Tools.WindowsInstallerXml.Msi; 29 | 30 | namespace LessMsi.Msi 31 | { 32 | public class ViewWrapper : IDisposable 33 | { 34 | public ViewWrapper(View underlyingView) 35 | { 36 | _underlyingView = underlyingView; 37 | CreateColumnInfos(); 38 | } 39 | 40 | private View _underlyingView; 41 | private ColumnInfo[] _columns; 42 | 43 | public ColumnInfo[] Columns 44 | { 45 | get { return _columns; } 46 | } 47 | 48 | /// 49 | /// Returns the index of the specified column. 50 | /// 51 | /// The name of the column to return an index for. 52 | public int ColumnIndex(string columnName) 53 | { 54 | for(var i=0; i < Columns.Length; i++) 55 | { 56 | if (string.Equals(columnName, Columns[i].Name, StringComparison.InvariantCultureIgnoreCase)) 57 | return i; 58 | } 59 | Debug.Fail("Column {0} not found.", columnName); 60 | return -1; 61 | } 62 | 63 | private void CreateColumnInfos() 64 | { 65 | const int MSICOLINFONAMES = 0; 66 | const int MSICOLINFOTYPES = 1; 67 | 68 | var colList = new List(); 69 | 70 | Record namesRecord; 71 | Record typesRecord; 72 | 73 | _underlyingView.GetColumnInfo(MSICOLINFONAMES, out namesRecord); 74 | _underlyingView.GetColumnInfo(MSICOLINFOTYPES, out typesRecord); 75 | using (namesRecord) 76 | using (typesRecord) 77 | { 78 | var fieldCount = namesRecord.GetFieldCount(); 79 | Debug.Assert(typesRecord.GetFieldCount() == fieldCount); 80 | 81 | for (var colIndex = 1; colIndex <= fieldCount; colIndex++) 82 | { 83 | colList.Add(new ColumnInfo(namesRecord.GetString(colIndex), typesRecord.GetString(colIndex))); 84 | } 85 | } 86 | _columns = colList.ToArray(); 87 | } 88 | 89 | 90 | private List _records; 91 | public IList Records 92 | { 93 | get 94 | { 95 | if (_records == null) 96 | { 97 | _records = new List(); 98 | Record sourceRecord = null; 99 | 100 | while (_underlyingView.Fetch(out sourceRecord)) 101 | { 102 | using (sourceRecord) 103 | { 104 | var values = new object[_columns.Length]; 105 | 106 | for (int i = 0; i < _columns.Length; i++) 107 | { 108 | if (_columns[i].IsString) 109 | values[i] = sourceRecord.GetString(i + 1); 110 | else if (_columns[i].IsInteger) 111 | values[i] = sourceRecord.GetInteger(i + 1); 112 | else if (_columns[i].IsStream) 113 | { 114 | var tempBuffer = new byte[_columns[i].Size + 1]; 115 | var allData = new byte[_columns[i].Size + 1]; 116 | int totalBytesRead = 0; 117 | int bytesReadThisCall; 118 | do 119 | { 120 | // It seems to read the Binary table with _columns[i].Size ==0 tempBuffer must be at least 1 in length or an ExecutionEngineException occurs. 121 | bytesReadThisCall = sourceRecord.GetStream(i + 1, tempBuffer, tempBuffer.Length); 122 | Buffer.BlockCopy(tempBuffer, 0, allData, totalBytesRead, bytesReadThisCall); 123 | totalBytesRead += bytesReadThisCall; 124 | Debug.Assert(bytesReadThisCall > 0); 125 | } while (bytesReadThisCall > 0 && (totalBytesRead < _columns[i].Size)); 126 | values[i] = allData; 127 | } 128 | else if (_columns[i].IsObject) 129 | { 130 | //we deliberately skip this case. Found this case in reading the _Tables table of some recent .msi files. 131 | } 132 | else 133 | { 134 | Debug.Fail("Unknown column type"); 135 | } 136 | } 137 | _records.Add(values); 138 | } 139 | } 140 | } 141 | return _records; 142 | } 143 | } 144 | 145 | #region IDisposable Members 146 | 147 | public void Dispose() 148 | { 149 | if (_underlyingView == null) 150 | return; 151 | _underlyingView.Close(); 152 | _underlyingView = null; 153 | } 154 | 155 | #endregion 156 | } 157 | } -------------------------------------------------------------------------------- /src/LessMsi.Core/OleStorage/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2017 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System.Runtime.InteropServices; 26 | 27 | namespace LessMsi.OleStorage 28 | { 29 | internal static class NativeMethods 30 | { 31 | [DllImport("ole32.dll")] 32 | internal static extern int StgIsStorageFile([MarshalAs(UnmanagedType.LPWStr)]string pwcsName); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/LessMsi.Core/OleStorage/README.md: -------------------------------------------------------------------------------- 1 | This is to read OLE/COM Structured Storage files or so-called "Compound Documents". 2 | 3 | References: 4 | * Overview/IStorage on MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/aa380017(v=vs.85).aspx 5 | * Sample: 6 | * https://msdn.microsoft.com/en-us/library/windows/desktop/aa380387(v=vs.85).aspx 7 | * P/Invoke: 8 | * http://pinvoke.net/default.aspx/Interfaces/IStorage.html 9 | * According to pinvoke.net System.IO.Packaging includes the necessary tools here: See http://www.pinvoke.net/default.aspx/ole32/StgOpenStorageEx.html -------------------------------------------------------------------------------- /src/LessMsi.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | 26 | using System.Reflection; 27 | using System.Runtime.CompilerServices; 28 | 29 | [assembly: AssemblyTitle("Less MSIérables (lessmsi) Core library")] 30 | [assembly: AssemblyProduct("Less MSIérables (lessmsi)")] 31 | -------------------------------------------------------------------------------- /src/LessMsi.Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/AboutBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Windows.Forms; 5 | 6 | namespace LessMsi.Gui 7 | { 8 | partial class AboutBox : Form 9 | { 10 | public AboutBox() 11 | { 12 | InitializeComponent(); 13 | this.Text = String.Format("About {0}", AssemblyTitle); 14 | this.labelProductName.Text = AssemblyProduct; 15 | this.labelVersion.Text = String.Format("Version {0}", AssemblyVersion); 16 | this.labelCopyright.Text = $"Copyright Scott Willeke © 2004-{DateTime.Now.Year}"; 17 | 18 | Icon = Properties.Resources.LessmsiIcon; 19 | } 20 | 21 | #region Assembly Attribute Accessors 22 | 23 | public string AssemblyTitle 24 | { 25 | get 26 | { 27 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false); 28 | if (attributes.Length > 0) 29 | { 30 | AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; 31 | if (titleAttribute.Title != "") 32 | { 33 | return titleAttribute.Title; 34 | } 35 | } 36 | return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); 37 | } 38 | } 39 | 40 | public string AssemblyVersion 41 | { 42 | get 43 | { 44 | return Assembly.GetExecutingAssembly().GetName().Version.ToString(); 45 | } 46 | } 47 | 48 | public string AssemblyDescription 49 | { 50 | get 51 | { 52 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 53 | if (attributes.Length == 0) 54 | { 55 | return ""; 56 | } 57 | return ((AssemblyDescriptionAttribute)attributes[0]).Description; 58 | } 59 | } 60 | 61 | public string AssemblyProduct 62 | { 63 | get 64 | { 65 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false); 66 | if (attributes.Length == 0) 67 | { 68 | return ""; 69 | } 70 | return ((AssemblyProductAttribute)attributes[0]).Product; 71 | } 72 | } 73 | 74 | public string AssemblyCopyright 75 | { 76 | get 77 | { 78 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); 79 | if (attributes.Length == 0) 80 | { 81 | return ""; 82 | } 83 | return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; 84 | } 85 | } 86 | 87 | public string AssemblyCompany 88 | { 89 | get 90 | { 91 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); 92 | if (attributes.Length == 0) 93 | { 94 | return ""; 95 | } 96 | return ((AssemblyCompanyAttribute)attributes[0]).Company; 97 | } 98 | } 99 | #endregion 100 | 101 | private void AboutBox_Load(object sender, EventArgs e) 102 | { 103 | var aboutRtf = GetType().Assembly.GetManifestResourceStream(GetType(), "aboutbox.rtf"); 104 | Debug.Assert(aboutRtf != null, "Failed to load aboutbox.rtf"); 105 | this.richTextBox.LoadFile(aboutRtf, RichTextBoxStreamType.RichText); 106 | } 107 | 108 | private void richTextBox_LinkClicked(object sender, LinkClickedEventArgs e) 109 | { 110 | Process.Start(e.LinkText); 111 | } 112 | 113 | private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 114 | { 115 | var text = ((LinkLabel) sender).Text; 116 | var link = text.Substring(e.Link.Start, e.Link.Length); 117 | Process.Start(link); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/ChangeLanguageForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace LessMsi.Gui 2 | { 3 | partial class ChangeLanguageForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.panel1 = new System.Windows.Forms.Panel(); 32 | this.saveBtn = new System.Windows.Forms.Button(); 33 | this.checkBoxesPanel = new System.Windows.Forms.Panel(); 34 | this.panel1.SuspendLayout(); 35 | this.SuspendLayout(); 36 | // 37 | // panel1 38 | // 39 | this.panel1.Controls.Add(this.saveBtn); 40 | this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; 41 | this.panel1.Location = new System.Drawing.Point(0, 297); 42 | this.panel1.Name = "panel1"; 43 | this.panel1.Size = new System.Drawing.Size(281, 100); 44 | this.panel1.TabIndex = 0; 45 | // 46 | // saveBtn 47 | // 48 | this.saveBtn.Location = new System.Drawing.Point(12, 53); 49 | this.saveBtn.Name = "saveBtn"; 50 | this.saveBtn.Size = new System.Drawing.Size(257, 39); 51 | this.saveBtn.TabIndex = 0; 52 | this.saveBtn.Text = "Save"; 53 | this.saveBtn.UseVisualStyleBackColor = true; 54 | this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click); 55 | // 56 | // checkBoxesPanel 57 | // 58 | this.checkBoxesPanel.Location = new System.Drawing.Point(0, 1); 59 | this.checkBoxesPanel.Name = "checkBoxesPanel"; 60 | this.checkBoxesPanel.Size = new System.Drawing.Size(281, 343); 61 | this.checkBoxesPanel.TabIndex = 1; 62 | // 63 | // ChangeLanguageForm 64 | // 65 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); 66 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 67 | this.ClientSize = new System.Drawing.Size(281, 397); 68 | this.Controls.Add(this.checkBoxesPanel); 69 | this.Controls.Add(this.panel1); 70 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 71 | this.MaximizeBox = false; 72 | this.MinimizeBox = false; 73 | this.Name = "ChangeLanguageForm"; 74 | this.Text = "ChangeLanguageForm"; 75 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChangeLanguageForm_FormClosing); 76 | this.panel1.ResumeLayout(false); 77 | this.ResumeLayout(false); 78 | 79 | } 80 | 81 | #endregion 82 | 83 | private System.Windows.Forms.Panel panel1; 84 | private System.Windows.Forms.Panel checkBoxesPanel; 85 | private System.Windows.Forms.Button saveBtn; 86 | } 87 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/ChangeLanguageForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Reflection; 6 | using System.Globalization; 7 | using System.Windows.Forms; 8 | using System.Collections.Generic; 9 | 10 | namespace LessMsi.Gui 11 | { 12 | public partial class ChangeLanguageForm : Form 13 | { 14 | private bool m_SaveBtnUsed; 15 | 16 | private string m_PreviousCheckedLang; 17 | private string m_CurrentCheckedLang; 18 | 19 | private Dictionary m_CheckBoxDict; 20 | private Dictionary m_CultureInfoDict; 21 | 22 | public ChangeLanguageForm() 23 | { 24 | InitializeComponent(); 25 | 26 | m_PreviousCheckedLang = string.Empty; 27 | m_CurrentCheckedLang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; 28 | 29 | setGUIData(); 30 | 31 | fillCultureInfoDict(); 32 | 33 | generateCheckboxes(); 34 | } 35 | 36 | public string NewSelectedLanguage => m_CurrentCheckedLang; 37 | 38 | private void setGUIData() 39 | { 40 | Icon = Properties.Resources.LessmsiIcon; 41 | Text = Resources.Languages.Strings.ChangeLang; 42 | } 43 | 44 | private void fillCultureInfoDict() 45 | { 46 | m_CultureInfoDict = new Dictionary(); 47 | 48 | string executingAssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 49 | var cultureDirectories = Directory.GetDirectories(executingAssemblyPath); 50 | 51 | var cultures = cultureDirectories 52 | .Select(Path.GetFileName) 53 | .Where(dir => !string.IsNullOrEmpty(dir)) 54 | .OrderBy(c => c) 55 | .ToList(); 56 | 57 | cultures.Add("en"); 58 | 59 | if (cultures.Any()) 60 | { 61 | foreach (var culture in cultures) 62 | { 63 | var cultureInfo = new CultureInfo(culture); 64 | m_CultureInfoDict.Add(culture, cultureInfo); 65 | } 66 | } 67 | } 68 | 69 | private void generateCheckboxes() 70 | { 71 | checkBoxesPanel.Controls.Clear(); 72 | m_CheckBoxDict = new Dictionary(); 73 | 74 | for (int i = 0; i < m_CultureInfoDict.Count; i++) 75 | { 76 | var currentCultureKey = m_CultureInfoDict.ElementAt(i).Key; 77 | var currentCultureInfo = m_CultureInfoDict.ElementAt(i).Value; 78 | 79 | var checkBox = new CheckBox 80 | { 81 | Name = currentCultureKey, 82 | Text = currentCultureInfo.DisplayName, 83 | AutoSize = true, 84 | Margin = new Padding(5), 85 | Location = new System.Drawing.Point(10, 25 * i), 86 | Checked = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == currentCultureKey 87 | }; 88 | 89 | checkBox.Click += (_, e) => OnCheckboxClick(checkBox, e); 90 | 91 | m_CheckBoxDict.Add(currentCultureKey, checkBox); 92 | 93 | checkBoxesPanel.Controls.Add(checkBox); 94 | } 95 | } 96 | 97 | private void saveBtn_Click(object sender, EventArgs e) 98 | { 99 | m_SaveBtnUsed = true; 100 | Close(); 101 | } 102 | 103 | private void OnCheckboxClick(object sender, EventArgs e) 104 | { 105 | CheckBox checkBox = sender as CheckBox; 106 | if (checkBox != null) 107 | { 108 | m_PreviousCheckedLang = m_CurrentCheckedLang; 109 | m_CurrentCheckedLang = checkBox.Name; 110 | 111 | if (m_CurrentCheckedLang == m_PreviousCheckedLang) 112 | { 113 | checkBox.Checked = true; 114 | return; 115 | } 116 | 117 | m_CheckBoxDict[m_PreviousCheckedLang].Checked = false; 118 | } 119 | } 120 | 121 | private void ChangeLanguageForm_FormClosing(object sender, FormClosingEventArgs e) 122 | { 123 | if (!m_SaveBtnUsed) 124 | { 125 | m_CurrentCheckedLang = string.Empty; 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/ChangeLanguageForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Extensions/DataGridViewExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace LessMsi.Gui.Extensions 5 | { 6 | public static class DataGridViewExtensions 7 | { 8 | /// 9 | /// Adjusts the width of all columns to fit the contents of all cells. 10 | /// This function takes care of the edgecase where the grid is not created yet. 11 | /// (For example, when it is filled with data before the control is shown.) 12 | /// 13 | /// The target control. 14 | public static void AutoResizeColumnsSafe(this DataGridView gridView) 15 | { 16 | if (gridView.IsHandleCreated) 17 | { 18 | gridView.AutoResizeColumns(); 19 | } 20 | else 21 | { 22 | // If the handle is not created yet we cannot resize columns, 23 | // so delay the auto-resizing to when the control is created. 24 | gridView.HandleCreated += Grid_HandleCreated; 25 | } 26 | } 27 | 28 | private static void Grid_HandleCreated(object sender, EventArgs e) 29 | { 30 | DataGridView dgv = sender as DataGridView; 31 | if (dgv != null) 32 | { 33 | dgv.HandleCreated -= Grid_HandleCreated; 34 | dgv.AutoResizeColumns(); 35 | } 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Extensions/MsiNativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace LessMsi.Gui.Extensions 6 | { 7 | internal static class MsiNativeMethods 8 | { 9 | [DllImport("msi.dll", CharSet = CharSet.Unicode, EntryPoint = "MsiSummaryInfoGetPropertyW", ExactSpelling = true)] 10 | internal static extern uint MsiSummaryInfoGetProperty(IntPtr summaryInfo, int property, out uint dataType, out int integerValue, ref System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Extensions/SummaryInformationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Tools.WindowsInstallerXml.Msi; 2 | using System; 3 | using System.Text; 4 | 5 | namespace LessMsi.Gui.Extensions 6 | { 7 | public static class SummaryInformationExtensions 8 | { 9 | /// 10 | /// There is a bug in Wix where it does not correctly return data with the FILETIME type. 11 | /// This extension method manually retrieves the value, and converts it to a DateTime. 12 | /// 13 | public static object GetPropertyFileTime(this SummaryInformation summaryInfo, int index) 14 | { 15 | uint iDataType; 16 | int integerValue, stringValueBufSize = 0; 17 | System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue = new System.Runtime.InteropServices.ComTypes.FILETIME(); 18 | StringBuilder stringValueBuf = new StringBuilder(); 19 | 20 | uint result = MsiNativeMethods.MsiSummaryInfoGetProperty(summaryInfo.InternalHandle, index, 21 | out iDataType, out integerValue, ref fileTimeValue, stringValueBuf, ref stringValueBufSize); 22 | 23 | if (result != 0) 24 | { 25 | throw new ArgumentNullException(); 26 | } 27 | 28 | switch ((Model.VT)iDataType) 29 | { 30 | case Model.VT.EMPTY: 31 | return string.Empty; 32 | case Model.VT.FILETIME: 33 | return DateTime.FromFileTime((((long)fileTimeValue.dwHighDateTime) << 32) | ((uint)fileTimeValue.dwLowDateTime)); 34 | default: 35 | throw new ArgumentNullException(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/ExtractionProgressDialog.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Drawing; 27 | using System.Windows.Forms; 28 | using LessMsi.Msi; 29 | 30 | namespace LessMsi.Gui 31 | { 32 | internal class ExtractionProgressDialog : Form 33 | { 34 | private ProgressBar _progressBar; 35 | private Label _label; 36 | 37 | public ExtractionProgressDialog(Form owner) 38 | { 39 | this.Owner = owner; 40 | 41 | this.ShowInTaskbar = false; 42 | this.TopMost = true; 43 | this.Size = new Size(320, 125); 44 | this.ControlBox = false; 45 | this.MaximizeBox = false; 46 | this.MinimizeBox = false; 47 | this.FormBorderStyle = FormBorderStyle.FixedDialog; 48 | this.StartPosition = FormStartPosition.Manual; 49 | 50 | this.Top = owner.Top + ((owner.Height - this.Height)/2); 51 | this.Left = owner.Left + ((owner.Width - this.Width)/2); 52 | 53 | this.DockPadding.Left = this.DockPadding.Right = 10; 54 | this.DockPadding.Top = this.DockPadding.Bottom = 10; 55 | 56 | _progressBar = new ProgressBar(); 57 | _progressBar.Dock = DockStyle.Bottom; 58 | _progressBar.Value = 0; 59 | this.Controls.Add(_progressBar); 60 | 61 | _label = new Label(); 62 | _label.Text = ""; 63 | _label.Dock = DockStyle.Fill; 64 | this.Controls.Add(_label); 65 | } 66 | 67 | public void UpdateProgress(IAsyncResult result) 68 | { 69 | if (result is Wixtracts.ExtractionProgress) 70 | UpdateProgress((Wixtracts.ExtractionProgress) result); 71 | } 72 | 73 | private delegate void UpdateProgressHandler(Wixtracts.ExtractionProgress progress); 74 | 75 | public void UpdateProgress(Wixtracts.ExtractionProgress progress) 76 | { 77 | if (this.InvokeRequired) 78 | { 79 | // This is ahack, but should be okay if needed to get around invoke 80 | this.Invoke(new UpdateProgressHandler(this.UpdateProgress), new object[] {progress}); 81 | return; 82 | } 83 | 84 | _progressBar.Minimum = 0; 85 | _progressBar.Maximum = progress.TotalFileCount; 86 | _progressBar.Value = progress.FilesExtractedSoFar; 87 | string details; 88 | if (progress.Activity == Wixtracts.ExtractionActivity.ExtractingFile) 89 | details = "Extracting file '" + progress.CurrentFileName + "'"; 90 | else 91 | details = Enum.GetName(typeof (Wixtracts.ExtractionActivity), progress.Activity); 92 | 93 | _label.Text = String.Format("Extracting ({0})...", details); 94 | this.Invalidate(true); 95 | this.Update(); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/IMainFormView.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Collections.Generic; 27 | using System.ComponentModel; 28 | using LessMsi.Gui.Model; 29 | 30 | namespace LessMsi.Gui 31 | { 32 | internal interface IMainFormView 33 | { 34 | /// 35 | /// Call to notify the view that a new file has been loaded into the UI. 36 | /// 37 | void NotifyNewFileLoaded(); 38 | /// 39 | /// Returns the currently specified MSI file in the UI. 40 | /// 41 | string SelectedMsiFileFullName { get; set; } 42 | /// 43 | /// The currently selected table headerText in the UI. 44 | /// 45 | string SelectedTableName { get; } 46 | /// 47 | /// Adds a column to the file grid. 48 | /// 49 | /// The headerText of the property the grid column is bound to. 50 | /// The header caption for the column. 51 | void AddFileGridColumn(string boundPropertyName, string headerText); 52 | /// 53 | /// Autosizes file grid columns based on content. 54 | /// 55 | void AutoSizeFileGridColumns(); 56 | /// 57 | /// Enables or disables the UI controls. 58 | /// 59 | void ChangeUiEnabled(bool doEnable); 60 | /// 61 | /// Returns the property currently selected in the UI or null if none selected. 62 | /// 63 | MsiPropertyInfo SelectedMsiProperty { get; } 64 | /// 65 | /// Sets or returns the description for the selected property in the UI. 66 | /// 67 | string PropertySummaryDescription {get; set;} 68 | /// 69 | /// Returns the streamInfo currently selected in the UI. 70 | /// 71 | StreamInfoView SelectedStreamInfo { get; } 72 | /// 73 | /// Shows an informational message to the user. 74 | /// 75 | void ShowUserMessageBox(string message); 76 | /// 77 | /// Shows an error to the user. 78 | /// 79 | void ShowUserError(string formatStr, params object[] args); 80 | /// 81 | /// Adds a column to the MSI table grid. 82 | /// 83 | void AddTableViewGridColumn(string headerText); 84 | /// 85 | /// Removes all columsn from the MSI Table grid. 86 | /// 87 | void ClearTableViewGridColumns(); 88 | /// 89 | /// Adds a row to the MSI table grid. 90 | /// 91 | void SetTableViewGridDataSource(IEnumerable values); 92 | 93 | /// 94 | /// Sort the MSI Table grid by the specified column 95 | /// 96 | void TableViewSortBy(string columnName, ListSortDirection direction); 97 | 98 | void SetPropertyGridDataSource(MsiPropertyInfo[] props); 99 | void AddPropertyGridColumn(string boundPropertyName, string headerText); 100 | /// 101 | /// Specifies the data source for the names of the streams in the streams selector. 102 | /// 103 | void SetStreamSelectorSource(IEnumerable streamNames); 104 | void SetCabContainedFileListSource(IEnumerable streamFiles); 105 | 106 | /// 107 | /// Start a wait cursor to indicate. 108 | /// 109 | /// An object that should be disposed when the operation is done. 110 | IDisposable StartWaitCursor(); 111 | 112 | /// 113 | /// Displays a status text in the bar at the bottom of the form. 114 | /// 115 | void StatusText(string text, string toolTip); 116 | } 117 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/Model/CabContainedFileView.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2017 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | 27 | namespace LessMsi.Gui.Model 28 | { 29 | /// 30 | /// Represents a file contained isnide of a Stream () when the stream is a Cab. 31 | /// 32 | internal sealed class CabContainedFileView 33 | { 34 | public CabContainedFileView(string name) 35 | { 36 | if (string.IsNullOrEmpty(name)) 37 | throw new ArgumentNullException("name"); 38 | this.Name = name; 39 | } 40 | 41 | public string Name { get; private set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Model/MsiFileItemView.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using LessMsi.Msi; 26 | 27 | namespace LessMsi.Gui.Model 28 | { 29 | /// 30 | /// Represensts an MSI file item (a file contained in an msi) in the UI. 31 | /// 32 | class MsiFileItemView 33 | { 34 | private readonly MsiFile _file; 35 | 36 | public MsiFileItemView(MsiFile msiDataFile) 37 | { 38 | _file = msiDataFile; 39 | } 40 | 41 | public string Name 42 | { 43 | get { return File.LongFileName; } 44 | } 45 | 46 | public string Directory 47 | { 48 | get { return File.Directory.GetPath(); } 49 | } 50 | 51 | public int Size 52 | { 53 | get { return File.FileSize; } 54 | } 55 | 56 | public string Version 57 | { 58 | get { return File.Version; } 59 | } 60 | 61 | public string Component 62 | { 63 | get { return File.Component; } 64 | } 65 | 66 | internal MsiFile File 67 | { 68 | get { return _file; } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Model/StreamInfoView.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2017 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | namespace LessMsi.Gui.Model 26 | { 27 | /// 28 | /// Used to model a OLE structured storage stream in the UI. 29 | /// 30 | internal sealed class StreamInfoView 31 | { 32 | private readonly string _name; 33 | private readonly string _displayName; 34 | 35 | public static StreamInfoView FromStream(System.IO.Packaging.StreamInfo si) 36 | { 37 | return new StreamInfoView(si.Name, OleStorage.OleStorageFile.IsCabStream(si)); 38 | } 39 | 40 | private StreamInfoView(string name, bool isCabStream) 41 | { 42 | _name = name; 43 | _displayName = OleStorage.OleStorageFile.DecodeName(name); 44 | IsCabStream = isCabStream; 45 | } 46 | 47 | public string Name 48 | { 49 | get { return _name; } 50 | } 51 | 52 | public string DisplayName 53 | { 54 | get { return _displayName; } 55 | } 56 | 57 | public bool IsCabStream { get; private set; } 58 | 59 | public string Label 60 | { 61 | get { return this.IsCabStream ? this.DisplayName + " (CAB)" : this.DisplayName; } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/PreferencesForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using LessMsi.Gui.Resources.Languages; 2 | using LessMsi.Gui.Windows.Forms; 3 | 4 | namespace LessMsi.Gui 5 | { 6 | partial class PreferencesForm 7 | { 8 | /// 9 | /// Required designer variable. 10 | /// 11 | private System.ComponentModel.IContainer components = null; 12 | 13 | /// 14 | /// Clean up any resources being used. 15 | /// 16 | /// true if managed resources should be disposed; otherwise, false. 17 | protected override void Dispose(bool disposing) 18 | { 19 | if (disposing && (components != null)) 20 | { 21 | components.Dispose(); 22 | } 23 | base.Dispose(disposing); 24 | } 25 | 26 | #region Windows Form Designer generated code 27 | 28 | /// 29 | /// Required method for Designer support - do not modify 30 | /// the contents of this method with the code editor. 31 | /// 32 | private void InitializeComponent() 33 | { 34 | this.cmdAddShortcut = new ElevationButton(); 35 | this.cmdRemoveShortcut = new ElevationButton(); 36 | this.btnOk = new System.Windows.Forms.Button(); 37 | this.SuspendLayout(); 38 | // 39 | // cmdAddShortcut 40 | // 41 | this.cmdAddShortcut.FlatStyle = System.Windows.Forms.FlatStyle.System; 42 | this.cmdAddShortcut.Location = new System.Drawing.Point(12, 16); 43 | this.cmdAddShortcut.Name = "cmdAddShortcut"; 44 | this.cmdAddShortcut.ShowElevationShield = true; 45 | this.cmdAddShortcut.Size = new System.Drawing.Size(344, 72); 46 | this.cmdAddShortcut.TabIndex = 1; 47 | this.cmdAddShortcut.Text = Strings.AddShortcutText; 48 | this.cmdAddShortcut.TextNote = Strings.AddShortcutTextNote; 49 | this.cmdAddShortcut.UseVisualStyleBackColor = true; 50 | this.cmdAddShortcut.Click += new System.EventHandler(this.cmdAddRemoveShortcut_Click); 51 | // 52 | // cmdRemoveShortcut 53 | // 54 | this.cmdRemoveShortcut.FlatStyle = System.Windows.Forms.FlatStyle.System; 55 | this.cmdRemoveShortcut.Location = new System.Drawing.Point(12, 94); 56 | this.cmdRemoveShortcut.Name = "cmdRemoveShortcut"; 57 | this.cmdRemoveShortcut.ShowElevationShield = true; 58 | this.cmdRemoveShortcut.Size = new System.Drawing.Size(344, 60); 59 | this.cmdRemoveShortcut.TabIndex = 2; 60 | this.cmdRemoveShortcut.Text = Strings.RemoveShortcutText; 61 | this.cmdRemoveShortcut.TextNote = Strings.RemoveShortcutTextNote; 62 | this.cmdRemoveShortcut.UseVisualStyleBackColor = true; 63 | this.cmdRemoveShortcut.Click += new System.EventHandler(this.cmdAddRemoveShortcut_Click); 64 | // 65 | // btnOk 66 | // 67 | this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 68 | this.btnOk.DialogResult = System.Windows.Forms.DialogResult.Cancel; 69 | this.btnOk.Location = new System.Drawing.Point(281, 171); 70 | this.btnOk.Name = "btnOk"; 71 | this.btnOk.Size = new System.Drawing.Size(75, 23); 72 | this.btnOk.TabIndex = 4; 73 | this.btnOk.Text = "OK"; 74 | this.btnOk.UseVisualStyleBackColor = true; 75 | this.btnOk.Click += new System.EventHandler(this.btnOk_Click); 76 | // 77 | // PreferencesForm 78 | // 79 | this.AcceptButton = this.btnOk; 80 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 81 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 82 | this.CancelButton = this.btnOk; 83 | this.ClientSize = new System.Drawing.Size(368, 206); 84 | this.Controls.Add(this.btnOk); 85 | this.Controls.Add(this.cmdRemoveShortcut); 86 | this.Controls.Add(this.cmdAddShortcut); 87 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 88 | this.MaximizeBox = false; 89 | this.MinimizeBox = false; 90 | this.Name = "PreferencesForm"; 91 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 92 | this.ResumeLayout(false); 93 | } 94 | 95 | #endregion 96 | 97 | private ElevationButton cmdAddShortcut; 98 | private ElevationButton cmdRemoveShortcut; 99 | private System.Windows.Forms.Button btnOk; 100 | 101 | } 102 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/PreferencesForm.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using LessMsi.Gui.Resources.Languages; 26 | using System; 27 | using System.Diagnostics; 28 | using System.IO; 29 | using System.Text; 30 | using System.Windows.Forms; 31 | 32 | namespace LessMsi.Gui 33 | { 34 | internal partial class PreferencesForm : Form 35 | { 36 | public PreferencesForm() 37 | { 38 | InitializeComponent(); 39 | 40 | this.Text = $"LessMSIerables {Strings.Preferences}"; 41 | } 42 | 43 | private void btnOk_Click(object sender, EventArgs e) 44 | { 45 | Close(); 46 | } 47 | 48 | private void cmdAddRemoveShortcut_Click(object sender, EventArgs e) 49 | { 50 | bool isAdding; 51 | 52 | if (sender == cmdAddShortcut) 53 | isAdding = true; 54 | else 55 | isAdding = false; 56 | 57 | /* FIX for http://code.google.com/p/lessmsi/issues/detail?id=11 58 | * This code below is funky because apparently Win32 requires us to escape double quotes on the command line when passing them through the command line. 59 | * So we have to actually espcape the escape char here to make sure double quotes are properly escaped 60 | * Explained more at http://bytes.com/topic/net/answers/745324-console-application-command-line-parameter-issue and http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx 61 | * 62 | * Also see https://github.com/activescott/lessmsi/wiki/Command-Line for commandline reference for the commands used below. 63 | */ 64 | const string escapedDoubleQuote = "\\" + "\""; 65 | 66 | var shellCommand = escapedDoubleQuote + GetLessMsiExeFile() + escapedDoubleQuote + " x " + escapedDoubleQuote + "%1" + escapedDoubleQuote; 67 | Debug.WriteLine("ShellCommand:[" + shellCommand + "]"); 68 | AddRemoveShortcut(isAdding, "extract", "Msi.Package", "Lessmsi &Extract Files", shellCommand); 69 | 70 | /* Fix for https://code.google.com/p/lessmsi/issues/detail?id=6&sort=-id 71 | */ 72 | shellCommand = escapedDoubleQuote + GetLessMsiExeFile() + escapedDoubleQuote + " o " + escapedDoubleQuote + "%1" + escapedDoubleQuote; 73 | Debug.WriteLine("ShellCommand:[" + shellCommand + "]"); 74 | AddRemoveShortcut(isAdding, "explore", "Msi.Package", "&Lessmsi Explore", shellCommand); 75 | } 76 | void AddRemoveShortcut(bool isAdding, string commandName, string fileClass, string caption, string shellCommand) 77 | { 78 | var thisExe = GetThisExeFile(); 79 | string shortcutHelperExe = Path.Combine(thisExe.Directory.FullName, "AddWindowsExplorerShortcut.exe"); 80 | if (!File.Exists(shortcutHelperExe)) 81 | { 82 | MessageBox.Show(this, 83 | $"{Strings.File} '" + Path.GetFileNameWithoutExtension(shortcutHelperExe) + 84 | $"' {Strings.SameDirMassage} lessmsi-gui.exe.", 85 | Strings.MissingFile, 86 | MessageBoxButtons.OK, 87 | MessageBoxIcon.Error); 88 | return; 89 | } 90 | var newProcess = new Process(); 91 | var info = new ProcessStartInfo(""); 92 | info.FileName = shortcutHelperExe; 93 | info.UseShellExecute = true; 94 | info.ErrorDialog = true; 95 | info.ErrorDialogParentHandle = this.Handle; 96 | //AddWindowsExplorerShortcut add|remove commandName fileClass [caption shellCommand] 97 | var args = new StringBuilder(); 98 | if (isAdding) 99 | args.Append("add"); 100 | else 101 | args.Append("remove"); 102 | args.Append(' ').Append(commandName); 103 | args.Append(' ').Append(fileClass); 104 | if (isAdding) 105 | { 106 | args.Append(" \"").Append(caption).Append("\""); 107 | args.Append(' ').Append('\"').Append(shellCommand).Append('\"'); 108 | } 109 | info.Arguments = args.ToString(); 110 | newProcess.StartInfo = info; 111 | newProcess.Start(); 112 | } 113 | 114 | static string GetLessMsiExeFile() 115 | { 116 | return Path.Combine(GetThisExeFile().Directory.FullName, "lessmsi.exe"); 117 | } 118 | 119 | static FileInfo GetThisExeFile() 120 | { 121 | return new FileInfo(typeof(PreferencesForm).Module.FullyQualifiedName); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Windows.Forms; 4 | 5 | namespace LessMsi.Gui 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main(string[] args) 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | 18 | MainForm form = new MainForm(args.FirstOrDefault()); 19 | Application.Run(form); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | 26 | using System.Reflection; 27 | 28 | [assembly: AssemblyTitle("Less MSIérables (lessmsi) GUI interface")] 29 | [assembly: AssemblyProduct("Less MSIérables (lessmsi)")] 30 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace LessMsi.Gui.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LessMsi.Gui.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 65 | /// 66 | internal static System.Drawing.Icon LessmsiIcon { 67 | get { 68 | object obj = ResourceManager.GetObject("LessmsiIcon", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace LessMsi.Gui.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | public global::System.Collections.Specialized.StringCollection RecentFiles { 29 | get { 30 | return ((global::System.Collections.Specialized.StringCollection)(this["RecentFiles"])); 31 | } 32 | set { 33 | this["RecentFiles"] = value; 34 | } 35 | } 36 | 37 | [global::System.Configuration.UserScopedSettingAttribute()] 38 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 39 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] 40 | public global::System.Drawing.Size LastRecordedAppSize { 41 | get { 42 | return ((global::System.Drawing.Size)(this["LastRecordedAppSize"])); 43 | } 44 | set { 45 | this["LastRecordedAppSize"] = value; 46 | } 47 | } 48 | 49 | [global::System.Configuration.UserScopedSettingAttribute()] 50 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 51 | [global::System.Configuration.DefaultSettingValueAttribute("10, 10")] 52 | public global::System.Drawing.Point LastRecordedAppLocation { 53 | get { 54 | return ((global::System.Drawing.Point)(this["LastRecordedAppLocation"])); 55 | } 56 | set { 57 | this["LastRecordedAppLocation"] = value; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0, 0 10 | 11 | 12 | 10, 10 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Resources/LessmsiIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/LessMsi.Gui/Resources/LessmsiIcon.ico -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/DisposableCursor.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Windows.Forms; 27 | 28 | namespace LessMsi.Gui.Windows.Forms 29 | { 30 | /// 31 | /// DisposableCursor allows you to use the C# using statement to return to a normal cursor. 32 | /// 33 | /// Simple Example of using the DisposableCursor with the C# using statement 34 | /// 35 | /// using (new Willeke.Shared.Windows.Forms.DisposableCursor(this)) 36 | /// { 37 | /// // Put the busy operation code here... 38 | /// System.Threading.Thread.Sleep(TimeSpan.FromSeconds(3)); 39 | /// } 40 | /// 41 | /// 42 | internal class DisposableCursor : IDisposable 43 | { 44 | // The cursor used before this DisposableCursor was initialized. 45 | private Cursor previousCursor; 46 | // The control this cursor instance is used with. 47 | private Control control; 48 | 49 | 50 | /// 51 | /// Initializes an instance of the DisposableCursor class with "Wait Cursor" displayed for the specified control. 52 | /// 53 | /// The control to display the cursor over. 54 | public DisposableCursor(Control control) 55 | :this(control, Cursors.WaitCursor) 56 | { 57 | } 58 | 59 | /// 60 | /// Initializes an instance of the DisposableCursor class with the specified cursor displayed for the specified control. 61 | /// 62 | /// The control to display the cursor over. 63 | /// The cursor to display while the mouse pointer is over the control. 64 | public DisposableCursor(Control control, Cursor newCursor) 65 | { 66 | if (control == null) 67 | throw new ArgumentNullException("control"); 68 | if (newCursor == null) 69 | throw new ArgumentNullException("cursor"); 70 | 71 | this.previousCursor = control.Cursor; 72 | this.control = control; 73 | control.Cursor = newCursor; 74 | control.Update(); 75 | } 76 | 77 | #region Implementation of IDisposable 78 | public void Dispose() 79 | { 80 | // Dispose the existing cursor (the one created by this class) 81 | //this.control.Cursor.Dispose();// DON'T dispose this. Aparently .NET doesn't like you disposing system cursors :D 82 | 83 | // Give the control back it's old cursor 84 | this.control.Cursor = this.previousCursor; 85 | } 86 | #endregion 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/ElevationButton.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Runtime.InteropServices; 27 | using System.Windows.Forms; 28 | 29 | namespace LessMsi.Gui.Windows.Forms 30 | { 31 | /// 32 | /// This button shows the windows Elevation symbol on it. Win32 refers to this as a "Command Link". 33 | /// 34 | internal class ElevationButton : Button 35 | { 36 | public ElevationButton() 37 | { 38 | FlatStyle = FlatStyle.System; 39 | _textNote = "noteText"; 40 | } 41 | 42 | protected override CreateParams CreateParams 43 | { 44 | get 45 | { 46 | CreateParams cp = base.CreateParams; 47 | cp.Style |= NativeMethods.BS_COMMANDLINK; 48 | return (cp); 49 | } 50 | } 51 | 52 | private string _textNote=""; 53 | public string TextNote 54 | { 55 | get { return _textNote; } 56 | set 57 | { 58 | _textNote = value; 59 | if (IsHandleCreated) 60 | NativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.BCM_SETNOTE, IntPtr.Zero, _textNote); 61 | } 62 | } 63 | 64 | bool _showElevationShield; 65 | public bool ShowElevationShield 66 | { 67 | get 68 | { 69 | return _showElevationShield; 70 | } 71 | set 72 | { 73 | _showElevationShield = value; 74 | NativeMethods.Button_SetElevationRequiredState(this, _showElevationShield); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/ElevationNativeMethods.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Diagnostics; 27 | using System.Runtime.InteropServices; 28 | using System.Windows.Forms; 29 | 30 | namespace LessMsi.Gui.Windows.Forms 31 | { 32 | internal class NativeMethods 33 | { 34 | public const int BS_COMMANDLINK = 0x0000000E; 35 | public const int BCM_SETNOTE = 0x00001609; 36 | const int BCM_FIRST = 0x1600; 37 | public const int BCM_SETSHIELD = (BCM_FIRST + 0x000C); 38 | 39 | /// 40 | /// Sets the elevation required state for a specified button or command link to display an elevated icon. 41 | /// 42 | /// 43 | /// 44 | /// http://msdn.microsoft.com/en-us/library/bb761865(VS.85).aspx 45 | /// 46 | /// #define BCM_FIRST 0x1600 // Button control messages 47 | /// // Macro to use on a button or command link to display an elevated icon 48 | /// #define BCM_SETSHIELD (BCM_FIRST + 0x000C) 49 | /// #define Button_SetElevationRequiredState(hwnd, fRequired) \ 50 | /// (LRESULT)SNDMSG((hwnd), BCM_SETSHIELD, 0, (LPARAM)fRequired) 51 | /// 52 | public static void Button_SetElevationRequiredState(ButtonBase button, bool show) 53 | { 54 | if (button == null) 55 | throw new ArgumentNullException("button"); 56 | 57 | button.FlatStyle = FlatStyle.System; 58 | 59 | var buttonHandle = new HandleRef(button, button.Handle); 60 | Trace.WriteLine("handle:" + buttonHandle.Handle); 61 | 62 | var lParam = new IntPtr(Int32.MaxValue - 1); 63 | IntPtr ret = SendMessage(buttonHandle, BCM_SETSHIELD, show ? new IntPtr(1) : IntPtr.Zero, ref lParam); 64 | Trace.WriteLine(ret.ToInt64()); 65 | } 66 | 67 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 68 | public static extern IntPtr SendMessage(HandleRef hWnd, Int32 Msg, IntPtr wParam, ref IntPtr lParam); 69 | 70 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 71 | public static extern IntPtr SendMessage(HandleRef hWnd, Int32 Msg, IntPtr wParam, string lParam); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/ErrorDialog.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace LessMsi.Gui.Windows.Forms 4 | { 5 | public partial class ErrorDialog : Form 6 | { 7 | public ErrorDialog() 8 | { 9 | InitializeComponent(); 10 | } 11 | 12 | public static void ShowError(IWin32Window owner, string message, string technicalDetails) 13 | { 14 | var dlg = new ErrorDialog(); 15 | dlg.txtErrorDetail.Text = string.Format("{0}\r\n\r\nTechnical Detail:\r\n\r\n{1}", message, technicalDetails); 16 | dlg.ShowDialog(owner); 17 | } 18 | 19 | private void lblPleaseReportLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 20 | { 21 | var target = "https://github.com/activescott/lessmsi/issues/"; 22 | System.Diagnostics.Process.Start(target); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/SearchPanel.Designer.cs: -------------------------------------------------------------------------------- 1 | using LessMsi.Gui.Resources.Languages; 2 | 3 | namespace LessMsi.Gui.Windows.Forms { 4 | partial class SearchPanel { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) { 15 | if (disposing && (components != null)) { 16 | components.Dispose(); 17 | } 18 | base.Dispose(disposing); 19 | } 20 | 21 | #region Component Designer generated code 22 | 23 | /// 24 | /// Required method for Designer support - do not modify 25 | /// the contents of this method with the code editor. 26 | /// 27 | private void InitializeComponent() { 28 | this.tbSearchText = new System.Windows.Forms.TextBox(); 29 | this.cancelButton = new System.Windows.Forms.Button(); 30 | this.label1 = new System.Windows.Forms.Label(); 31 | this.SuspendLayout(); 32 | // 33 | // tbSearchText 34 | // 35 | this.tbSearchText.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 36 | this.tbSearchText.Dock = System.Windows.Forms.DockStyle.Fill; 37 | this.tbSearchText.Location = new System.Drawing.Point(48, 2); 38 | this.tbSearchText.Name = "tbSearchText"; 39 | this.tbSearchText.Size = new System.Drawing.Size(222, 20); 40 | this.tbSearchText.TabIndex = 0; 41 | this.tbSearchText.TextChanged += new System.EventHandler(this.tbSearchText_TextChanged); 42 | this.tbSearchText.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tbSearchText_KeyDown); 43 | // 44 | // cancelButton 45 | // 46 | this.cancelButton.Dock = System.Windows.Forms.DockStyle.Right; 47 | this.cancelButton.Location = new System.Drawing.Point(270, 2); 48 | this.cancelButton.Name = "cancelButton"; 49 | this.cancelButton.Size = new System.Drawing.Size(28, 18); 50 | this.cancelButton.TabIndex = 1; 51 | this.cancelButton.Text = "X"; 52 | this.cancelButton.UseVisualStyleBackColor = true; 53 | // 54 | // label1 55 | // 56 | this.label1.Dock = System.Windows.Forms.DockStyle.Left; 57 | this.label1.Location = new System.Drawing.Point(2, 2); 58 | this.label1.Name = "label1"; 59 | this.label1.Size = new System.Drawing.Size(46, 18); 60 | this.label1.TabIndex = 2; 61 | this.label1.Text = Strings.Search; 62 | this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 63 | // 64 | // SearchPanel 65 | // 66 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; 67 | this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 68 | this.Controls.Add(this.tbSearchText); 69 | this.Controls.Add(this.label1); 70 | this.Controls.Add(this.cancelButton); 71 | this.MaximumSize = new System.Drawing.Size(999, 26); 72 | this.Name = "SearchPanel"; 73 | this.Padding = new System.Windows.Forms.Padding(2); 74 | this.Size = new System.Drawing.Size(300, 22); 75 | this.ResumeLayout(false); 76 | this.PerformLayout(); 77 | 78 | } 79 | 80 | #endregion 81 | 82 | private System.Windows.Forms.TextBox tbSearchText; 83 | private System.Windows.Forms.Button cancelButton; 84 | private System.Windows.Forms.Label label1; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/SearchPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace LessMsi.Gui.Windows.Forms 7 | { 8 | internal partial class SearchPanel : UserControl 9 | { 10 | public event EventHandler SearchTermChanged; 11 | 12 | /// 13 | /// Raised when the search term box is canceled. Any filtering done based on the search can be cleared at this point. 14 | /// 15 | public event EventHandler SearchCanceled; 16 | private Control dataGridToAttachTo; 17 | 18 | public SearchPanel() 19 | { 20 | this.Visible = false; 21 | InitializeComponent(); 22 | } 23 | 24 | /// 25 | /// Performs the search of the specified data grid. 26 | /// 27 | /// This DataGrid control the search is performed on. 28 | /// This control will position itself within the area of the grid. 29 | /// Handler for search term change. 30 | /// When the user cancels the search the caller can use this handler to clean something up. 31 | public void SearchDataGrid(Control dataGridToAttachTo, 32 | EventHandler searchTermChangedHandler, 33 | EventHandler cancelSearchingEventHandler 34 | ) 35 | { 36 | Debug.Assert(this.dataGridToAttachTo == null || object.ReferenceEquals(this.dataGridToAttachTo, dataGridToAttachTo), "expected always to be the same data grid? Something odd here."); 37 | this.dataGridToAttachTo = dataGridToAttachTo; 38 | dataGridToAttachTo.Parent.Controls.Add(this); 39 | SetPreferredLocationAndSize(); 40 | this.SearchTermChanged += searchTermChangedHandler; 41 | this.SearchCanceled += cancelSearchingEventHandler; 42 | this.Parent.Resize += (sender, args) => SetPreferredLocationAndSize(); 43 | this.cancelButton.Click += (sender, args) => this.CancelSearch(); 44 | this.BringToFront(); 45 | this.Visible = true; 46 | this.tbSearchText.Focus(); 47 | } 48 | 49 | private void SetPreferredLocationAndSize() 50 | { 51 | this.SuspendLayout(); 52 | this.Width = 300;//this.Width = dataGridToAttachTo.Width; 53 | this.ClientSize = new Size(this.ClientSize.Width, PreferredClientHeight); 54 | this.ResumeLayout(true); 55 | this.Location = new Point(dataGridToAttachTo.Left, dataGridToAttachTo.Height - this.Height); 56 | } 57 | 58 | private int PreferredClientHeight 59 | { 60 | get { return this.tbSearchText.Bottom + this.Padding.Bottom; } 61 | } 62 | 63 | private void tbSearchText_TextChanged(object sender, EventArgs e) 64 | { 65 | if (SearchTermChanged != null) 66 | { 67 | SearchTermChanged(this, new SearchTermChangedEventArgs() {SearchString = tbSearchText.Text}); 68 | } 69 | } 70 | 71 | public void CancelSearch() 72 | { 73 | if (!this.Visible) 74 | return; 75 | this.tbSearchText.Text = ""; 76 | this.Hide(); 77 | if (SearchCanceled != null) 78 | SearchCanceled(this, EventArgs.Empty); 79 | } 80 | 81 | private void tbSearchText_KeyDown(object sender, KeyEventArgs e) 82 | { 83 | if (e.KeyCode == Keys.Escape) 84 | { 85 | CancelSearch(); 86 | e.Handled = true; 87 | e.SuppressKeyPress = true; 88 | } 89 | } 90 | } 91 | 92 | internal class SearchTermChangedEventArgs : EventArgs 93 | { 94 | public string SearchString { get; set; } 95 | } 96 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/SearchPanel.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/SortableBindingList.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System; 26 | using System.Collections.Generic; 27 | using System.ComponentModel; 28 | using System.Diagnostics; 29 | using System.Linq; 30 | 31 | namespace LessMsi.Gui.Windows.Forms 32 | { 33 | /// 34 | /// Implements a simple that provides sorting ability. 35 | /// 36 | /// Useful to provide a sortable . 37 | internal sealed class SortableBindingList : BindingList 38 | { 39 | private readonly IEnumerable _originalItems; 40 | private PropertyDescriptor _sortProperty; 41 | private ListSortDirection _sortDirection; 42 | 43 | public SortableBindingList(IEnumerable items) 44 | { 45 | _originalItems = items; 46 | ResetItems(); 47 | } 48 | 49 | private void ResetItems() 50 | { 51 | RaiseListChangedEvents = false; 52 | if (Items.Count > 0) 53 | Items.Clear(); 54 | foreach (var i in _originalItems) 55 | { 56 | Add(i); 57 | } 58 | RaiseListChangedEvents = true; 59 | RaiseListSortingChanged(); 60 | } 61 | 62 | protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) 63 | { 64 | _sortProperty = prop; 65 | _sortDirection = direction; 66 | Debug.WriteLine("ApplySortCore"); 67 | RaiseListChangedEvents = false; 68 | Func selector = item => prop.GetValue(item); 69 | var sorted = direction == ListSortDirection.Ascending ? Items.OrderBy(selector).ToList() : Items.OrderByDescending(selector).ToList(); 70 | Items.Clear(); 71 | foreach (var item in sorted) 72 | { 73 | Items.Add(item); 74 | } 75 | RaiseListChangedEvents = true; 76 | RaiseListSortingChanged(); 77 | } 78 | 79 | protected override PropertyDescriptor SortPropertyCore 80 | { 81 | get { Debug.WriteLine("SortPropertyCore"); return _sortProperty; } 82 | } 83 | 84 | protected override ListSortDirection SortDirectionCore 85 | { 86 | get { return _sortDirection; } 87 | } 88 | protected override bool IsSortedCore 89 | { 90 | get { return _sortProperty != null; } 91 | } 92 | 93 | private void RaiseListSortingChanged() 94 | { 95 | OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); 96 | } 97 | 98 | protected override void RemoveSortCore() 99 | { 100 | ResetItems(); 101 | _sortProperty = null; 102 | RaiseListSortingChanged(); 103 | } 104 | 105 | protected override bool SupportsSortingCore 106 | { 107 | get { return true; } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/Windows.Forms/WinFormsHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | using System.Linq; 9 | 10 | namespace LessMsi.Gui.Windows.Forms 11 | { 12 | /// 13 | /// Contains misc generic helper methods for dealing with controls. 14 | /// 15 | public class WinFormsHelper 16 | { 17 | /// 18 | /// Allows a c# using statement to be used for the contract. 19 | /// 20 | public static IDisposable BeginUiUpdate(ISupportInitialize control) 21 | { 22 | control.BeginInit(); 23 | return new ControlInitializationToken(control); 24 | } 25 | 26 | private struct ControlInitializationToken : IDisposable 27 | { 28 | private ISupportInitialize _control; 29 | 30 | public ControlInitializationToken(ISupportInitialize control) 31 | { 32 | _control = control; 33 | } 34 | 35 | public void Dispose() 36 | { 37 | _control.EndInit(); 38 | } 39 | } 40 | 41 | public static void FlashDataGridRow(DataGridViewRow row) 42 | { 43 | var grid = row.DataGridView; 44 | var r = grid.GetRowDisplayRectangle(row.Index, false); 45 | r.Inflate(0, 2); 46 | using (var g = grid.CreateGraphics()) 47 | { 48 | g.DrawRectangle(SystemPens.Highlight, r); 49 | } 50 | var t = new Timer(); 51 | t.Tick += (obj, ea) => 52 | { 53 | r.Inflate(2, 2); 54 | grid.Invalidate(r); 55 | ((Timer)obj).Stop(); 56 | }; 57 | t.Interval = 120; 58 | t.Start(); 59 | } 60 | 61 | public static void CopySelectedDataGridRowsToClipboard(DataGridView grid) 62 | { 63 | if (grid != null && grid.SelectedRows.Count > 0) 64 | { 65 | const string SEPERATOR = "\t"; 66 | var sb = new StringBuilder(); 67 | var cols = grid.Columns.Cast(); 68 | var colNames = string.Join(SEPERATOR, cols.Select(c => c.HeaderText).ToArray()); 69 | sb.AppendLine(colNames); 70 | //NOTE: the grid.SelectedRows is in a different order than they are in the grid. So enumerating .Rows is better 71 | for (int iRow = 0; iRow < grid.Rows.Count; iRow++) 72 | { 73 | DataGridViewRow row = grid.Rows[iRow]; 74 | if (row.Selected) 75 | { 76 | int i = 0; 77 | foreach (DataGridViewCell cell in row.Cells) 78 | { 79 | if (i++ > 0) 80 | sb.Append(SEPERATOR); 81 | sb.Append(cell.Value); 82 | } 83 | 84 | FlashDataGridRow(row); 85 | sb.AppendLine(); 86 | } 87 | } 88 | Clipboard.SetText(sb.ToString()); 89 | } 90 | } 91 | 92 | public static IWin32Window GetParentForm(Control control) 93 | { 94 | while (!(control is Form)) 95 | { 96 | control = control.Parent; 97 | } 98 | Debug.Assert(control is Form, "expected control to be form!"); 99 | return (control as Form); 100 | } 101 | 102 | public static Rectangle GetScreenRect(Control ctl) 103 | { 104 | var r = new Rectangle(0, 0, ctl.Width, ctl.Height); 105 | while (!(ctl is Form)) 106 | { 107 | r.X += ctl.Left; 108 | r.Y += ctl.Top; 109 | ctl = ctl.Parent; 110 | } 111 | Debug.Assert(ctl is Form, "expected control to be form!"); 112 | return (ctl as Form).RectangleToScreen(r); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/aboutbox.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1265 2 | {\fonttbl\f0\fnil\fcharset0 Calibri;} 3 | {\colortbl;\red255\green255\blue255;\red0\green0\blue255;} 4 | {\info 5 | {\author scott}}\margl1440\margr1440\vieww12540\viewh16140\viewkind1 6 | \deftab720 7 | \pard\pardeftab720\ri0\sl276\slmult1\sa200 8 | 9 | \f0\fs22 \cf0 Created by {\field{\*\fldinst{HYPERLINK "http://scott.willeke.com"}}{\fldrslt \cf2 \ul \ulc2 Scott Willeke}} with contributions from {\field{\*\fldinst{HYPERLINK "https://github.com/zippy1981"}}{\fldrslt \cf2 \ul \ulc2 Justin Dearing}}, {\field{\*\fldinst{HYPERLINK "https://github.com/AGBrown"}}{\fldrslt \cf2 \ul \ulc2 Andrew Brown}}, {\field{\*\fldinst{HYPERLINK "https://github.com/bastianeicher"}}{\fldrslt \cf2 \ul \ulc2 Bastian Eicher}} and others.\ 10 | This program uses a slightly modified version of the {\field{\*\fldinst{HYPERLINK "http://www.cabextract.org.uk/libmspack/"}}{\fldrslt \cf2 \ul \ulc2 libmspack}} library. The modified libmspack source code is available from the lessmsi site at {\field{\*\fldinst{HYPERLINK "https://github.com/activescott/libmspack4n"}}{\fldrslt \cf2 \ul \ulc2 https://github.com/activescott/libmspack4n}}.\ 11 | } -------------------------------------------------------------------------------- /src/LessMsi.Gui/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 0, 0 16 | 17 | 18 | 10, 10 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/LessMsi.Gui/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/LessMsi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34728.123 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExplorerShortcutHelper", "ExplorerShortcutHelper\ExplorerShortcutHelper.csproj", "{13F99803-59EF-4DEA-BE1C-68561A427B08}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LessMsi.Tests", "Lessmsi.Tests\LessMsi.Tests.csproj", "{78591E05-341F-450F-B215-D23E959F49F1}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {3D9AB5F1-2773-4403-8E50-771721572508} = {3D9AB5F1-2773-4403-8E50-771721572508} 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{79970F6C-B1A1-4094-8D3E-FC97A39D15C3}" 14 | ProjectSection(SolutionItems) = preProject 15 | ..\appveyor.yml = ..\appveyor.yml 16 | build.bat = build.bat 17 | build.sh = build.sh 18 | clean.bat = clean.bat 19 | CommonAssemblyInfo.cs = CommonAssemblyInfo.cs 20 | LICENSE = LICENSE 21 | ..\README.md = ..\README.md 22 | test.bat = test.bat 23 | EndProjectSection 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{6917653A-0B43-4033-90AF-9ED60A7FEEDD}" 26 | ProjectSection(SolutionItems) = preProject 27 | .build\chocolateyInstall.ps1 = .build\chocolateyInstall.ps1 28 | .build\lessmsi.msbuild = .build\lessmsi.msbuild 29 | .build\lessmsi.nuspec = .build\lessmsi.nuspec 30 | .build\nuget-restore.bat = .build\nuget-restore.bat 31 | EndProjectSection 32 | EndProject 33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LessMsi.Core", "LessMsi.Core\LessMsi.Core.csproj", "{1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C}" 34 | EndProject 35 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LessMsi.Gui", "LessMsi.Gui\LessMsi.Gui.csproj", "{3D9AB5F1-2773-4403-8E50-771721572508}" 36 | ProjectSection(ProjectDependencies) = postProject 37 | {13F99803-59EF-4DEA-BE1C-68561A427B08} = {13F99803-59EF-4DEA-BE1C-68561A427B08} 38 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C} = {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C} 39 | EndProjectSection 40 | EndProject 41 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LessMsi.Cli", "LessMsi.Cli\LessMsi.Cli.csproj", "{7ED32F80-2B7B-4F90-ADE9-22B8F3935C63}" 42 | ProjectSection(ProjectDependencies) = postProject 43 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C} = {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C} 44 | EndProjectSection 45 | EndProject 46 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{3398DA89-7F43-4564-B2ED-FE35E1555D9C}" 47 | ProjectSection(SolutionItems) = preProject 48 | .nuget\NuGet.Config = .nuget\NuGet.Config 49 | .nuget\NuGet.exe = .nuget\NuGet.exe 50 | .nuget\NuGet.targets = .nuget\NuGet.targets 51 | .nuget\packages.config = .nuget\packages.config 52 | EndProjectSection 53 | EndProject 54 | Global 55 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 56 | Debug|x86 = Debug|x86 57 | Release|x86 = Release|x86 58 | EndGlobalSection 59 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 60 | {13F99803-59EF-4DEA-BE1C-68561A427B08}.Debug|x86.ActiveCfg = Debug|x86 61 | {13F99803-59EF-4DEA-BE1C-68561A427B08}.Debug|x86.Build.0 = Debug|x86 62 | {13F99803-59EF-4DEA-BE1C-68561A427B08}.Release|x86.ActiveCfg = Release|x86 63 | {13F99803-59EF-4DEA-BE1C-68561A427B08}.Release|x86.Build.0 = Release|x86 64 | {78591E05-341F-450F-B215-D23E959F49F1}.Debug|x86.ActiveCfg = Debug|x86 65 | {78591E05-341F-450F-B215-D23E959F49F1}.Debug|x86.Build.0 = Debug|x86 66 | {78591E05-341F-450F-B215-D23E959F49F1}.Release|x86.ActiveCfg = Release|x86 67 | {78591E05-341F-450F-B215-D23E959F49F1}.Release|x86.Build.0 = Release|x86 68 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C}.Debug|x86.ActiveCfg = Debug|x86 69 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C}.Debug|x86.Build.0 = Debug|x86 70 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C}.Release|x86.ActiveCfg = Release|x86 71 | {1FD916F2-1DF6-42F6-9AD4-83565FBF8E5C}.Release|x86.Build.0 = Release|x86 72 | {3D9AB5F1-2773-4403-8E50-771721572508}.Debug|x86.ActiveCfg = Debug|x86 73 | {3D9AB5F1-2773-4403-8E50-771721572508}.Debug|x86.Build.0 = Debug|x86 74 | {3D9AB5F1-2773-4403-8E50-771721572508}.Release|x86.ActiveCfg = Release|x86 75 | {3D9AB5F1-2773-4403-8E50-771721572508}.Release|x86.Build.0 = Release|x86 76 | {7ED32F80-2B7B-4F90-ADE9-22B8F3935C63}.Debug|x86.ActiveCfg = Debug|x86 77 | {7ED32F80-2B7B-4F90-ADE9-22B8F3935C63}.Debug|x86.Build.0 = Debug|x86 78 | {7ED32F80-2B7B-4F90-ADE9-22B8F3935C63}.Release|x86.ActiveCfg = Release|x86 79 | {7ED32F80-2B7B-4F90-ADE9-22B8F3935C63}.Release|x86.Build.0 = Release|x86 80 | EndGlobalSection 81 | GlobalSection(SolutionProperties) = preSolution 82 | HideSolutionNode = FALSE 83 | EndGlobalSection 84 | GlobalSection(ExtensibilityGlobals) = postSolution 85 | SolutionGuid = {5F4F0736-7C32-4F2F-AC07-2EB5A808FCED} 86 | EndGlobalSection 87 | EndGlobal 88 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/CompareEntriesResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LessMsi.Tests 4 | { 5 | public struct CompareEntriesResult : IEquatable 6 | { 7 | public bool AreEntriesEqual { get; private set; } 8 | public string ErrorMessage { get; private set; } 9 | 10 | public CompareEntriesResult(bool areEntriesEqual, string errorMessage) 11 | { 12 | AreEntriesEqual = areEntriesEqual; 13 | ErrorMessage = errorMessage; 14 | } 15 | 16 | public bool Equals(CompareEntriesResult other) 17 | { 18 | return other.AreEntriesEqual == this.AreEntriesEqual && String.Compare(other.ErrorMessage, this.ErrorMessage, StringComparison.InvariantCulture) == 0; 19 | } 20 | 21 | public override bool Equals(Object obj) 22 | { 23 | if (obj == null) 24 | { 25 | return false; 26 | } 27 | 28 | if (!(obj is CompareEntriesResult)) 29 | { 30 | return false; 31 | } 32 | 33 | return this.Equals((CompareEntriesResult)obj); 34 | } 35 | 36 | public override int GetHashCode() 37 | { 38 | var code = this.AreEntriesEqual.GetHashCode(); 39 | code ^= this.ErrorMessage?.GetHashCode() ?? 0; 40 | return code; 41 | } 42 | 43 | public static bool operator ==(CompareEntriesResult a, CompareEntriesResult b) 44 | { 45 | if (((object)a) == null || ((object)b) == null) 46 | { 47 | return Object.Equals(a, b); 48 | } 49 | 50 | return a.Equals(b); 51 | } 52 | 53 | public static bool operator !=(CompareEntriesResult a, CompareEntriesResult b) 54 | { 55 | return !(a == b); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/Lessmsi.Tests/FileEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace LessMsi.Tests 6 | { 7 | [DebuggerDisplay("{Path}")] 8 | public sealed class FileEntry : IEquatable 9 | { 10 | /// 11 | /// Initializes a new FileEntry. 12 | /// 13 | /// The initial value for . 14 | /// The initial value for . 15 | /// 16 | /// 17 | /// 18 | public FileEntry(string path, long size, DateTime creationTime, DateTime lastWriteTime, FileAttributes attributes) 19 | { 20 | Size = size; 21 | Path = path; 22 | this.CreationTime = creationTime; 23 | this.LastWriteTime = lastWriteTime; 24 | this.Attributes = attributes; 25 | } 26 | 27 | /// 28 | /// Initializes a new FileEntry 29 | /// 30 | /// The file this object represents. 31 | /// 32 | /// The root of the path of the specified file that should be removed to ensure that the output is a relative portion of the file. 33 | /// Essentially the value of will be changed by stripping of the begining portion of this file. 34 | /// 35 | public FileEntry(FileInfo file, string basePathToRemove) 36 | { 37 | Size = file.Length; 38 | 39 | if (file.FullName.StartsWith(basePathToRemove, StringComparison.InvariantCultureIgnoreCase)) 40 | Path = file.FullName.Substring(basePathToRemove.Length); 41 | else 42 | { 43 | Path = file.FullName; 44 | Debug.Fail("Why would this happen? Normally the file should be rooted in that path."); 45 | } 46 | 47 | 48 | this.CreationTime = file.CreationTime; 49 | this.LastWriteTime = file.LastWriteTime; 50 | this.Attributes = file.Attributes; 51 | } 52 | 53 | public FileAttributes Attributes { get; private set; } 54 | public DateTime LastWriteTime { get; private set; } 55 | public DateTime CreationTime { get; private set; } 56 | public string Path { get; private set; } 57 | public long Size { get; private set; } 58 | 59 | #region IEquatable 60 | public bool Equals(FileEntry other) 61 | { 62 | return this.Equals(other, false); 63 | } 64 | 65 | public bool Equals(FileEntry other, bool flatExtractionFlag) 66 | { 67 | return isSizeEqual(other) && 68 | isPathEqual(other) && 69 | areAttributesEqual(other) && 70 | isLastWriteTimeEqual(other) && 71 | (flatExtractionFlag || isCreationTime(other)) 72 | ; 73 | } 74 | #endregion 75 | 76 | #region Checking methods 77 | private bool isSizeEqual(FileEntry other) 78 | { 79 | return this.Size == other.Size; 80 | } 81 | 82 | private bool isPathEqual(FileEntry other) 83 | { 84 | return string.Equals(this.Path, other.Path, StringComparison.InvariantCultureIgnoreCase); 85 | } 86 | 87 | private bool areAttributesEqual(FileEntry other) 88 | { 89 | return this.Attributes == other.Attributes; 90 | } 91 | 92 | private bool isLastWriteTimeEqual(FileEntry other) 93 | { 94 | return this.LastWriteTime == other.LastWriteTime; 95 | } 96 | 97 | private bool isCreationTime(FileEntry other) 98 | { 99 | return this.CreationTime == other.CreationTime; 100 | } 101 | #endregion 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/GUITests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using LessMsi.Gui; 3 | using System.Threading; 4 | using System.Globalization; 5 | 6 | namespace LessMsi.Tests 7 | { 8 | public class GUITests : TestBase 9 | { 10 | private CultureInfo originalUICulture; 11 | private CultureInfo originalCulture; 12 | 13 | [Fact] 14 | public void CheckUIStrings() 15 | { 16 | checkEnglishUIStrings(); 17 | checkItalianUIStrings(); 18 | } 19 | 20 | private void checkEnglishUIStrings() 21 | { 22 | setCustomLocale("en"); 23 | 24 | var form = new MainForm(string.Empty); 25 | 26 | // test if form was created successfully 27 | Assert.NotNull(form); 28 | 29 | // check buttons strings 30 | Assert.Equal("Select &All", form.btnSelectAll.Text); 31 | Assert.Equal("&Unselect All", form.btnUnselectAll.Text); 32 | Assert.Equal("E&xtract", form.btnExtract.Text); 33 | 34 | // check strip menu items strings 35 | Assert.Equal("&Edit", form.editToolStripMenuItem.Text); 36 | Assert.Equal("&Preferences", form.preferencesToolStripMenuItem.Text); 37 | 38 | revertToOriginalLocale(); 39 | } 40 | 41 | private void checkItalianUIStrings() 42 | { 43 | setCustomLocale("it-IT"); 44 | 45 | var form = new MainForm(string.Empty); 46 | 47 | // test if form was created successfully 48 | Assert.NotNull(form); 49 | 50 | // check buttons strings 51 | Assert.Equal("Seleiona &tutto", form.btnSelectAll.Text); 52 | Assert.Equal("&Deseleziona tutto", form.btnUnselectAll.Text); 53 | Assert.Equal("E&strai", form.btnExtract.Text); 54 | 55 | // check strip menu items strings 56 | Assert.Equal("&Modifica", form.editToolStripMenuItem.Text); 57 | Assert.Equal("&Preferenze", form.preferencesToolStripMenuItem.Text); 58 | 59 | revertToOriginalLocale(); 60 | } 61 | 62 | private void setCustomLocale(string locale) 63 | { 64 | originalCulture = CultureInfo.CurrentCulture; 65 | originalUICulture = CultureInfo.CurrentUICulture; 66 | 67 | var culture = CultureInfo.CreateSpecificCulture(locale); 68 | CultureInfo.CurrentCulture = culture; 69 | CultureInfo.CurrentUICulture = culture; 70 | } 71 | 72 | private void revertToOriginalLocale() 73 | { 74 | CultureInfo.CurrentCulture = originalCulture; 75 | CultureInfo.CurrentUICulture = originalUICulture; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/Lessmsi.Tests/MiscTestsNUnit.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LessMsi.Tests 4 | { 5 | // We use Test Collections to prevent files get locked by seperate threads: http://xunit.github.io/docs/running-tests-in-parallel.html 6 | [Collection("NUnit - 2.5.2.9222.msi")] 7 | public class MiscTestsNunit : TestBase 8 | { 9 | [Fact] 10 | public void NUnit() 11 | { 12 | ExtractAndCompareToMaster("NUnit-2.5.2.9222.msi"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/MspTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LessMsi.Tests 4 | { 5 | public class MspTests: TestBase 6 | { 7 | [Fact] 8 | public void MsXml5() 9 | { 10 | ExpectTables("msxml5.msp", new[] { "MsiPatchMetadata", "MsiPatchSequence" }); 11 | // Cannot test properties yet, since they are internal in LessMsi.Gui! 12 | ExpectStreamCabFiles("msxml5.msp", true); 13 | } 14 | 15 | [Fact] 16 | public void WPF2_32() 17 | { 18 | ExpectTables("WPF2_32.msp", new[] { "MsiPatchMetadata", "MsiPatchSequence" }); 19 | ExpectStreamCabFiles("WPF2_32.msp", true); 20 | } 21 | 22 | [Fact] 23 | public void SQL2008_AS() 24 | { 25 | ExpectTables("SQL2008_AS.msp", new[] { "MsiPatchSequence" }); 26 | ExpectStreamCabFiles("SQL2008_AS.msp", true); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/OleStorageTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | using Xunit; 4 | 5 | namespace LessMsi.Tests 6 | { 7 | public class OleStorageTests: TestBase 8 | { 9 | [DebuggerHidden] 10 | static string FromRawBytes(byte[] data) 11 | { 12 | // Do not rely on encoding, build it manually! 13 | Assert.Equal(0, data.Length % 2); 14 | var sb = new StringBuilder(data.Length / 2); 15 | for (int n = 0; n < data.Length; n+= 2) 16 | { 17 | char ch = (char)(data[n] << 8); 18 | ch = (char)(ch | data[n + 1]); 19 | sb.Append(ch); 20 | } 21 | return sb.ToString(); 22 | } 23 | 24 | [Fact] 25 | public void TestNameDecode() 26 | { 27 | // All names extracted from WPF2_32.msp using a c++ scratch project 28 | var testdata = new[] 29 | { 30 | new { Result = "_Columns", Data = new byte[]{0x48, 0x40, 0x3b, 0x3f, 0x43, 0xf2, 0x44, 0x38, 0x45, 0xb1} }, // STGTY_STREAM 31 | new { Result = "_Tables", Data = new byte[]{0x48, 0x40, 0x3f, 0x7f, 0x41, 0x64, 0x42, 0x2f, 0x48, 0x36 } }, // STGTY_STREAM 32 | new { Result = "T1ToU1", Data = new byte[]{0x00, 0x54, 0x00, 0x31, 0x00, 0x54, 0x00, 0x6f, 0x00, 0x55, 0x00, 0x31 } }, // STGTY_STORAGE 33 | new { Result = "#T1ToU1", Data = new byte[]{0x00, 0x23, 0x00, 0x54, 0x00, 0x31, 0x00, 0x54, 0x00, 0x6f, 0x00, 0x55, 0x00, 0x31 } }, // STGTY_STORAGE 34 | new { Result = "PCW_CAB_NetFX", Data = new byte[]{0x3b, 0x19, 0x47, 0xe0, 0x3a, 0x8c, 0x47, 0xcb, 0x42, 0x17, 0x3b, 0xf7, 0x48, 0x21 } }, // STGTY_STREAM 35 | new { Result = "_StringData", Data = new byte[]{0x48, 0x40, 0x3f, 0x3f, 0x45, 0x77, 0x44, 0x6c, 0x3b, 0x6a, 0x45, 0xe4, 0x48, 0x24 } }, // STGTY_STREAM 36 | new { Result = "_StringPool", Data = new byte[]{0x48, 0x40, 0x3f, 0x3f, 0x45, 0x77, 0x44, 0x6c, 0x3e, 0x6a, 0x44, 0xb2, 0x48, 0x2f } }, // STGTY_STREAM 37 | new { Result = "MsiPatchMetadata", Data = new byte[]{0x48, 0x40, 0x45, 0x96, 0x3e, 0x6c, 0x45, 0xe4, 0x42, 0xe6, 0x42, 0x16, 0x41, 0x37, 0x41, 0x27, 0x41, 0x37 } }, // STGTY_STREAM 38 | new { Result = "MsiPatchSequence", Data = new byte[]{0x48, 0x40, 0x45, 0x96, 0x3e, 0x6c, 0x45, 0xe4, 0x42, 0xe6, 0x42, 0x1c, 0x46, 0x34, 0x44, 0x68, 0x42, 0x26 } }, // STGTY_STREAM 39 | new { Result = "DigitalSignature", Data = new byte[]{0x00, 0x05, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67, 0x00, 0x69, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x53, 0x00, 0x69, 0x00, 0x67, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x74, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65 } }, // STGTY_STREAM 40 | new { Result = "SummaryInformation", Data = new byte[]{0x00, 0x05, 0x00, 0x53, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x79, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e } }, // STGTY_STREAM 41 | }; 42 | 43 | foreach(var entry in testdata) 44 | { 45 | string tmp = FromRawBytes(entry.Data); 46 | Assert.Equal(entry.Result, OleStorage.OleStorageFile.DecodeName(tmp)); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Permission is hereby granted, free of charge, to any person obtaining 2 | // a copy of this software and associated documentation files (the 3 | // "Software"), to deal in the Software without restriction, including 4 | // without limitation the rights to use, copy, modify, merge, publish, 5 | // distribute, sublicense, and/or sell copies of the Software, and to 6 | // permit persons to whom the Software is furnished to do so, subject to 7 | // the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be 10 | // included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Copyright (c) 2004-2013 Scott Willeke (http://scott.willeke.com) 21 | // 22 | // Authors: 23 | // Scott Willeke (scott@willeke.com) 24 | // 25 | using System.Reflection; 26 | 27 | // General Information about an assembly is controlled through the following 28 | // set of attributes. Change these attribute values to modify the information 29 | // associated with an assembly. 30 | [assembly: AssemblyTitle("Lessmsi.Tests")] 31 | [assembly: AssemblyDescription("")] 32 | [assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] // Disable other files get locked by seperate threads: http://xunit.github.io/docs/running-tests-in-parallel.html -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/Extract3Args.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | C:\projects\lessmsi\src\Lessmsi.Tests\bin\Debug\Ex3\SourceDir\PFiles\NUnit 2.5.2\samples\csharp\money\cs-money.build,363,2009-02-07T20:33:16.0000000,2009-02-07T20:33:16.0000000,Archive 3 | C:\projects\lessmsi\src\Lessmsi.Tests\bin\Debug\Ex3\SourceDir\PFiles\NUnit 2.5.2\doc\requiresMTA.html,5095,2009-08-10T17:30:50.0000000,2009-08-10T17:30:50.0000000,Archive 4 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/Extract3ArgsRelativePath.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | C:\projects\lessmsi\src\Lessmsi.Tests\bin\Debug\Ex3\SourceDir\PFiles\NUnit 2.5.2\samples\csharp\money\cs-money.build,363,2009-02-07T20:33:16.0000000,2009-02-07T20:33:16.0000000,Archive 3 | C:\projects\lessmsi\src\Lessmsi.Tests\bin\Debug\Ex3\SourceDir\PFiles\NUnit 2.5.2\doc\requiresMTA.html,5095,2009-08-10T17:30:50.0000000,2009-08-10T17:30:50.0000000,Archive 4 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/ExtractOnlySomeFiles.msi.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | \SourceDir\PFiles\NUnit 2.5.2\samples\Extensibility\Core\SampleSuiteExtension\Tests\SampleSuiteExtensionTests.cs,838,2007-02-23T11:11:52.0000000,2007-02-23T11:11:52.0000000,Archive 3 | \SourceDir\PFiles\NUnit 2.5.2\doc\img\testOutputOptions.jpg,38632,2008-09-14T20:54:08.0000000,2008-09-14T20:54:08.0000000,Archive 4 | \SourceDir\PFiles\NUnit 2.5.2\bin\net-2.0\NUnitTests.config,3386,2008-02-22T20:26:06.0000000,2008-02-22T20:26:06.0000000,Archive 5 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/FlatOverwriteExtract3Args.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | \cs-money.build,363,2024-07-01T22:15:45.5397931,2009-02-07T20:33:16.0000000,Archive 3 | \requiresMTA.html,5095,2024-07-01T22:15:45.5383848,2009-08-10T17:30:50.0000000,Archive 4 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/FlatRenameExtract3Args.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | \cs-money.build,363,2024-07-01T22:16:47.4318578,2009-02-07T20:33:16.0000000,Archive 3 | \requiresMTA.html,5095,2024-07-01T22:16:47.4299764,2009-08-10T17:30:50.0000000,Archive 4 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/OverwriteExtract3Args.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | \SourceDir\Common Files\Apple\Mobile Device Support\api-ms-win-core-file-l1-1-0.dll,22208,2023-11-09T08:17:24.0000000,2023-11-09T08:17:24.0000000,Archive 3 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/msi_with_external_cab.msi.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | \SourceDir\PFiles\~TestMSIWithExternalCab\create_msi_with_external_cab.wxs,970,2013-12-05T22:51:46.0000000,2013-12-05T22:51:46.0000000,Archive 3 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/ExpectedOutput/putty-0.68-installer.msi.expected.csv: -------------------------------------------------------------------------------- 1 | Path,Size,CreationTime,LastWriteTime,Attributes 2 | \SourceDir\PFiles\PuTTY\LICENCE,1338,2017-02-18T17:10:22.0000000,2017-02-18T17:10:22.0000000,Archive 3 | \SourceDir\PFiles\PuTTY\pageant.exe,278392,2017-02-18T17:14:06.0000000,2017-02-18T17:14:06.0000000,Archive 4 | \SourceDir\PFiles\PuTTY\plink.exe,514424,2017-02-18T17:14:08.0000000,2017-02-18T17:14:08.0000000,Archive 5 | \SourceDir\PFiles\PuTTY\pscp.exe,525176,2017-02-18T17:14:08.0000000,2017-02-18T17:14:08.0000000,Archive 6 | \SourceDir\PFiles\PuTTY\psftp.exe,535416,2017-02-18T17:14:10.0000000,2017-02-18T17:14:10.0000000,Archive 7 | \SourceDir\PFiles\PuTTY\putty.chm,280032,2017-02-18T17:14:32.0000000,2017-02-18T17:14:32.0000000,Archive 8 | \SourceDir\PFiles\PuTTY\putty.exe,713592,2017-02-18T17:14:10.0000000,2017-02-18T17:14:10.0000000,Archive 9 | \SourceDir\PFiles\PuTTY\puttygen.exe,358264,2017-02-18T17:14:12.0000000,2017-02-18T17:14:12.0000000,Archive 10 | \SourceDir\PFiles\PuTTY\README.txt,1892,2017-02-18T17:10:14.0000000,2017-02-18T17:10:14.0000000,Archive 11 | \SourceDir\PFiles\PuTTY\website.url,103,2017-02-18T17:10:16.0000000,2017-02-18T17:10:16.0000000,Archive 12 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/AppleMobileDeviceSupport64.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/AppleMobileDeviceSupport64.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/ExtractOnlySomeFiles.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/ExtractOnlySomeFiles.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/IviNetSharedComponents32_Fx20_1.3.0.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/IviNetSharedComponents32_Fx20_1.3.0.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/NUnit-2.5.2.9222.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/NUnit-2.5.2.9222.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/Path With Spaces/spaces example.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/Path With Spaces/spaces example.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/SQL2008_AS.msp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/SQL2008_AS.msp -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/Slik-Subversion-1.6.6-x64.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/Slik-Subversion-1.6.6-x64.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/VBRuntime.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/VBRuntime.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/WPF2_32.msp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/WPF2_32.msp -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/X86 Debuggers And Tools-x86_en-us.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/X86 Debuggers And Tools-x86_en-us.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/long-directory-name/very/unusually/long/directory/name/with/cream/sugar/and/chocolate/topping/python-2.7.3.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/long-directory-name/very/unusually/long/directory/name/with/cream/sugar/and/chocolate/topping/python-2.7.3.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/msi_with_external_cab.cab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/msi_with_external_cab.cab -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/msi_with_external_cab.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/msi_with_external_cab.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/msxml5.msp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/msxml5.msp -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/putty-0.68-installer.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/putty-0.68-installer.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/python-2.7.3.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/python-2.7.3.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/vcredis1.cab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/vcredis1.cab -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/MsiInput/vcredist.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/lessmsi/ca8ca506d194ea710208c129701cba5bf850db5a/src/Lessmsi.Tests/TestFiles/MsiInput/vcredist.msi -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/create_msi_with_external_cab.cmd: -------------------------------------------------------------------------------- 1 | @SET THISPATH=%~dp0 2 | @SET THISFILENAME=%~n0 3 | 4 | @REM: Builds a simple setup from the WIX script (.wsx). See following references for more info: 5 | @REM: http://www.codeproject.com/Tips/105638/A-quick-introduction-Create-an-MSI-installer-with 6 | @REM: http://wix.tramontana.co.hu/tutorial/getting-started/the-software-package 7 | @REM: http://wixtoolset.org/documentation/manual/v3/ 8 | @REM: http://wixtoolset.org/documentation/manual/v3/overview/light.html 9 | 10 | 11 | @SET OUTPUT_MSI=msi_with_external_cab.msi 12 | @SET OUTPUT_CAB=msi_with_external_cab.cab 13 | 14 | c:\tools\wix\candle.exe .\create_msi_with_external_cab.wxs 15 | 16 | c:\tools\wix\light.exe -out msi_with_external_cab.msi create_msi_with_external_cab.wixobj 17 | 18 | move %OUTPUT_MSI% .\MsiInput\ 19 | move %OUTPUT_CAB% .\MsiInput\ -------------------------------------------------------------------------------- /src/Lessmsi.Tests/TestFiles/create_msi_with_external_cab.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Lessmsi.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/build.bat: -------------------------------------------------------------------------------- 1 | @setlocal 2 | @set /P TEMPVER= Enter the version number to build for: 3 | 4 | msbuild.exe .\.build\lessmsi.msbuild /p:TheVersion=%TEMPVER% /fileLogger 5 | -------------------------------------------------------------------------------- /src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # http://dlafferty.blogspot.ru/2013/08/building-your-microsoft-solution-with.html 4 | # Mono ships with an empty certificate store. The store needs to be populated with common certificates in order for HTTPS to work. 5 | # Use the mozroots tool to do this. 6 | mozroots --import --sync 7 | 8 | # This is to compile the project on travis-ci which is linux only. Although we can't run tests on linux, at least a good compile on linux will help ensure code always compiles. 9 | # using p:TargetFrameworkProfile= defaults to .NET 3.5 FULL profile instead of Client Profile. MOno does not support client profile! 10 | 11 | CONFIG=Release 12 | PROPS="/p:OS=mono /p:TargetFrameworkProfile= /p:Configuration=$CONFIG" 13 | 14 | # compile projects 15 | xbuild $PROPS ./ExplorerShortcutHelper/ExplorerShortcutHelper.csproj 16 | xbuild $PROPS ./LessMsi.Core/LessMsi.Core.csproj 17 | xbuild $PROPS ./LessMsi.Cli/LessMsi.Cli.csproj 18 | xbuild $PROPS ./LessMsi.Gui/LessMsi.Gui.csproj 19 | 20 | # compile tests 21 | # clear PostBuildEvent since the commands in LessMsi.Tests.csproj file are windows specific 22 | xbuild $PROPS /p:PostBuildEvent='' ./Lessmsi.Tests/LessMsi.Tests.csproj 23 | 24 | # copy test files 25 | # cp -R ./Lessmsi.Tests/TestFiles ./Lessmsi.Tests/bin/$CONFIG/ 26 | # running tests on linux does not work since lessmsi interops with msi.dll which available only on windows 27 | # nunit-console ./Lessmsi.Tests/bin/$CONFIG/LessMsi.Tests.dll 28 | -------------------------------------------------------------------------------- /src/clean.bat: -------------------------------------------------------------------------------- 1 | rd /S /Q ..\bin 2 | rd /S /Q .\LessMsi.Core\obj 3 | rd /S /Q .\LessMsi.Cli\obj 4 | rd /S /Q .\LessMsi.Gui\obj 5 | rd /S /Q .\LessMsi.Gui\bin 6 | rd /S /Q .\ExplorerShortcutHelper\obj 7 | 8 | rd /S /Q .\Lessmsi.Tests\bin 9 | rd /S /Q .\Lessmsi.Tests\obj 10 | 11 | 12 | rd /S /Q .\.deploy 13 | -------------------------------------------------------------------------------- /src/packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/test.bat: -------------------------------------------------------------------------------- 1 | @setlocal 2 | 3 | packages\xunit.runner.console.2.1.0\tools\xunit.console.x86.exe .\.deploy\LessMsi.Tests.dll -verbose 4 | --------------------------------------------------------------------------------