├── .clang-format
├── .gitattributes
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Directory.Build.props
├── Directory.CppBuild.props
├── LICENSE
├── NOTICE.txt
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── Solution.props
├── TestingScenarios.md
├── ToolingVersions.props
├── WindowsAdvancedSettings.sln
├── build
├── Build.cmd
├── EnsureOutputLayout.props
├── SyncMirror-Steps.yml
├── SyncMirroredRepository.yml
├── Test.cmd
├── TriggerReleaseBuild.yml
├── TriggerStagingBuild.yml
├── azure-pipelines.yml
├── cppversion
│ ├── version.h
│ ├── version.rc
│ └── version.vcxitems
├── nuget.config.internal
├── scripts
│ ├── Build.ps1
│ ├── CertSignAndInstall.ps1
│ ├── Create-AppxBundle.ps1
│ ├── CreateBuildInfo.ps1
│ ├── Test.ps1
│ ├── UnbundleStubPackage.ps1
│ └── Unstub.ps1
├── store
│ ├── PDPs
│ │ └── en-us
│ │ │ └── PDP.xml
│ ├── SBConfig.json
│ └── media
│ │ ├── de-DE
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── en-us
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── es-ES
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── fr-FR
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── it-IT
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── ja-JP
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── ko-KR
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── pt-BR
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── ru-RU
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ ├── zh-CN
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
│ │ └── zh-TW
│ │ ├── AdvancedSettings.png
│ │ └── FileExplorerSourceIntegration.png
└── templates
│ ├── EsrpSigning-Steps.yml
│ └── publish-symbolrequestprod-api.yml
├── codeAnalysis
├── GlobalSuppressions.cs
├── Rules.ruleset
├── StubSuppressions.cs
├── StyleCop.json
└── format_sources.ps1
├── exclusion.dic
├── nuget.config
├── src
├── AdvancedSettings
│ ├── Assets
│ │ ├── AdvancedSettings.ico
│ │ ├── Dev
│ │ │ ├── MedTile.png
│ │ │ ├── SmallTile.png
│ │ │ ├── SplashScreen.png
│ │ │ ├── SplashScreen.scale-100.png
│ │ │ ├── StoreLogo.png
│ │ │ ├── splashscreen.contrast-black.png
│ │ │ ├── splashscreen.contrast-black_scale-100.png
│ │ │ ├── splashscreen.contrast-white.png
│ │ │ └── splashscreen.contrast-white_scale-100.png
│ │ ├── Fonts
│ │ │ ├── AMCIcons.ttf
│ │ │ ├── CascadiaMono.ttf
│ │ │ ├── DevHome.ttf
│ │ │ └── README.md
│ │ ├── InitializationPage
│ │ │ └── AppList.scale-400.png
│ │ └── Production
│ │ │ ├── MedTile.png
│ │ │ ├── SmallTile.png
│ │ │ ├── SplashScreen.png
│ │ │ ├── SplashScreen.scale-100.png
│ │ │ ├── StoreLogo.png
│ │ │ ├── splashscreen.contrast-black.png
│ │ │ ├── splashscreen.contrast-black_scale-100.png
│ │ │ ├── splashscreen.contrast-white.png
│ │ │ └── splashscreen.contrast-white_scale-100.png
│ ├── NOTICE.txt
│ ├── NativeMethods.txt
│ ├── Package-Dev.appxmanifest
│ ├── Package.appinstaller
│ ├── Package.appxmanifest
│ ├── Program.cs
│ ├── Properties
│ │ └── launchsettings.json
│ ├── Setup.cs
│ ├── Strings
│ │ └── en-us
│ │ │ └── Resources.resw
│ ├── TemplateStudio.xml
│ ├── WindowsAdvancedSettings.csproj
│ ├── app.manifest
│ └── appsettings.json
├── Common
│ ├── Helpers
│ │ ├── DirectoryHelper.cs
│ │ ├── FileService.cs
│ │ ├── GitCommandRunnerResultInfo.cs
│ │ ├── Json.cs
│ │ ├── Logging.cs
│ │ ├── Resources.cs
│ │ ├── RuntimeHelper.cs
│ │ └── SettingsStorageExtensions.cs
│ ├── NativeMethods.txt
│ ├── PublishProfiles
│ │ ├── win-arm64.pubxml
│ │ ├── win-x64.pubxml
│ │ └── win-x86.pubxml
│ └── WindowsAdvancedSettings.Common.csproj
├── FileExplorerGitIntegration
│ ├── FileExplorerGitIntegration.csproj
│ ├── GitLocalRepositoryProviderFactory`1.cs
│ ├── Helpers
│ │ └── Resources.cs
│ ├── Models
│ │ ├── CommitLogCache.cs
│ │ ├── CommitWrapper.cs
│ │ ├── GitConfiguration.cs
│ │ ├── GitDetect.cs
│ │ ├── GitDetectStatus.cs
│ │ ├── GitExeceutableConfigOptions.cs
│ │ ├── GitExecute.cs
│ │ ├── GitLocalRepository.cs
│ │ ├── GitLocalRepositoryProviderFactory.cs
│ │ ├── GitLocalRepositoryProviderServer.cs
│ │ ├── GitRepositoryStatus.cs
│ │ ├── GitStatusEntry.cs
│ │ ├── LruCacheDictionary.cs
│ │ ├── RepositoryCache.cs
│ │ ├── RepositoryWrapper.cs
│ │ ├── StatusCache.cs
│ │ ├── ThrottledTask.cs
│ │ └── WslIntegrator.cs
│ ├── NativeMethods.txt
│ ├── Program.cs
│ ├── Strings
│ │ └── en-us
│ │ │ └── Resources.resw
│ └── appsettings_FileExplorerGitIntegration.json
└── FileExplorerSourceControlIntegration
│ ├── FileExplorerSourceControlIntegration.csproj
│ ├── NativeMethods.txt
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── SourceControlProvider.cs
│ ├── SourceControlProviderFactory`1.cs
│ ├── SourceControlProviderServer.cs
│ └── appsettings_FileExplorerSourceControl.json
└── test
├── AdvancedSettings.Tester
├── AdvancedSettings.Tester.csproj
├── Assets
│ ├── LockScreenLogo.scale-200.png
│ ├── SplashScreen.scale-200.png
│ ├── Square150x150Logo.scale-200.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Square44x44Logo.targetsize-24_altform-unplated.png
│ ├── StoreLogo.png
│ └── Wide310x150Logo.scale-200.png
├── ConfigureFolderPath.cs
├── Package.appxmanifest
├── Program.cs
├── Properties
│ └── launchSettings.json
├── README.md
├── app.manifest
└── appsettings_tester.json
├── FileExplorerGitIntegration.UnitTest
├── FileExplorerGitIntegration.UnitTest.csproj
├── GitCommandRunnerTests.cs
├── GitLocalRepositoryProviderUnitTests.cs
├── GitSubmoduleUnitTests.cs
├── GlobalUsings.cs
└── WslIntegratorUnitTests.cs
└── FileExplorerSourceControlIntegrationUnitTest
├── FileExplorerSourceControlIntegrationUnitTest.csproj
└── RepositoryTrackingServiceUnitTest.cs
/.clang-format:
--------------------------------------------------------------------------------
1 |
2 | AccessModifierOffset: -4
3 | AlignAfterOpenBracket: Align
4 | #AllowAllArgumentsOnNextLine: false
5 | AlignConsecutiveAssignments: false
6 | AlignConsecutiveDeclarations: false
7 | #AllowAllConstructorInitializersOnNextLine: false
8 | AlignEscapedNewlines: Left
9 | AlignOperands: true
10 | AlignTrailingComments: false
11 | AllowAllParametersOfDeclarationOnNextLine: false
12 | AllowShortFunctionsOnASingleLine: Inline
13 | AllowShortCaseLabelsOnASingleLine: false
14 | AllowShortIfStatementsOnASingleLine: false
15 | #AllowShortLambdasOnASingleLine: Inline
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterReturnType: None
18 | AlwaysBreakBeforeMultilineStrings: false
19 | AlwaysBreakTemplateDeclarations: Yes
20 | BinPackArguments: false
21 | BinPackParameters: false
22 | BraceWrapping:
23 | AfterCaseLabel: true
24 | AfterClass: true
25 | AfterControlStatement: true
26 | AfterEnum: true
27 | AfterFunction: true
28 | AfterNamespace: true
29 | AfterObjCDeclaration: true
30 | AfterStruct: true
31 | AfterUnion: true
32 | AfterExternBlock: true
33 | BeforeCatch: true
34 | BeforeElse: true
35 | IndentBraces: false
36 | SplitEmptyFunction: true
37 | SplitEmptyRecord: true
38 | SplitEmptyNamespace: true
39 | BreakBeforeBinaryOperators: None
40 | BreakBeforeBraces: Custom
41 | BreakBeforeTernaryOperators: false
42 | BreakConstructorInitializers: AfterColon
43 | BreakInheritanceList: AfterColon
44 | ColumnLimit: 0
45 | CommentPragmas: "suppress"
46 | CompactNamespaces: false
47 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
48 | ConstructorInitializerIndentWidth: 4
49 | ContinuationIndentWidth: 4
50 | Cpp11BracedListStyle: false
51 | DerivePointerAlignment: false
52 | FixNamespaceComments: false
53 | IncludeBlocks: Regroup
54 | IncludeCategories:
55 | - Regex: '^.*(precomp|pch|stdafx)'
56 | Priority: -1
57 | - Regex: '^".*"'
58 | Priority: 1
59 | - Regex: '^<.*>'
60 | Priority: 2
61 | - Regex: '.*'
62 | Priority: 3
63 | IndentCaseLabels: false
64 | IndentPPDirectives: None
65 | IndentWidth: 4
66 | IndentWrappedFunctionNames: false
67 | KeepEmptyLinesAtTheStartOfBlocks: false
68 | ForEachMacros: ['TEST_CLASS', 'TEST_METHOD']
69 | MacroBlockBegin: "TEST_METHOD|TEST_CLASS|BEGIN_TEST_METHOD_PROPERTIES|BEGIN_MODULE|BEGIN_TEST_CLASS|BEGIN_TEST_METHOD"
70 | MacroBlockEnd: "END_TEST_METHOD_PROPERTIES|END_MODULE|END_TEST_CLASS|END_TEST_METHOD"
71 | MaxEmptyLinesToKeep: 1
72 | NamespaceIndentation: All
73 | PointerAlignment: Left
74 | ReflowComments: false
75 | SortIncludes: false
76 | SortUsingDeclarations: true
77 | SpaceAfterCStyleCast: false
78 | #SpaceAfterLogicalNot: false
79 | SpaceAfterTemplateKeyword: false
80 | SpaceBeforeAssignmentOperators: true
81 | SpaceBeforeCpp11BracedList: false
82 | SpaceBeforeCtorInitializerColon: true
83 | SpaceBeforeInheritanceColon: true
84 | SpaceBeforeParens: ControlStatements
85 | SpaceBeforeRangeBasedForLoopColon: true
86 | SpaceInEmptyParentheses: false
87 | SpacesBeforeTrailingComments: 1
88 | SpacesInAngles: false
89 | SpacesInCStyleCastParentheses: false
90 | SpacesInContainerLiterals: false
91 | SpacesInParentheses: false
92 | SpacesInSquareBrackets: false
93 | Standard: Cpp11
94 | TabWidth: 4
95 | UseTab: Never
96 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=crlf
2 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Copyright (C) 2022 Microsoft Corporation
5 | Microsoft Corp.
6 | Copyright (C) 2022 Microsoft Corporation
7 | DevHome
8 | Microsoft Corporation
9 | en-US
10 | x64;x86;ARM64
11 | DevHome
12 | true
13 | Recommended
14 | $(Platform)
15 | false
16 | Debug;Release;Debug_FailFast
17 | 10.0.22621.34
18 |
19 |
20 |
26 |
27 | true
28 |
29 |
30 |
31 | <_PropertySheetDisplayName>DevHome.Root.Props
32 | $(MsbuildThisFileDirectory)\Cpp.Build.props
33 | Dev
34 | $(DefineConstants);STABLE_BUILD
35 |
36 |
37 |
38 | $(DefineConstants);DEBUG
39 |
40 |
41 |
42 | $(DefineConstants);DEBUG;DEBUG_FAILFAST
43 |
44 |
45 |
46 |
47 | all
48 | runtime; build; native; contentfiles; analyzers; buildtransitive
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | False
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Directory.CppBuild.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(Platform)
4 | x86
5 | $(SolutionDir)tools\bin\$(CppPlatformTarget)\$(Configuration)\
6 | $(CppBaseOutDir)$(MSBuildProjectName)\
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to the Windows Advanced Settings repo
2 |
3 | This repository contains the source code for:
4 |
5 | * [Windows Advanced Settings](https://aka.ms/WindowsAdvancedSettings)
6 |
7 | ## Installing and running Windows Advanced Settings
8 |
9 | > **Note**: Windows Advanced Settings requires Windows 11 21H2 (build 22000) or later.
10 |
11 | ### Microsoft Store
12 |
13 | You can also install the Windows Advanced Settings directly from its [Microsoft Store listing](https://aka.ms/WindowsAdvancedSettings).
14 |
15 | ### Other install methods
16 |
17 | #### Via GitHub
18 |
19 | For users who are unable to install the Windows Advanced Settings from the Microsoft Store, released builds can be manually downloaded from this repository's [Releases page](https://github.com/microsoft/WindowsAdvancedSettings/releases).
20 |
21 | ---
22 |
23 | ## Windows Advanced Settings overview
24 |
25 | Please take a few minutes to review the overview below before diving into the code:
26 |
27 | ---
28 |
29 | ## Documentation
30 |
31 | Documentation for the Windows Advanced Settings can be found at https://aka.ms/WindowsAdvancedSettingsDocs.
32 |
33 | ---
34 |
35 | ## Contributing
36 |
37 | We are excited to work alongside you, our amazing community, to build and enhance the Windows Advanced Settings!
38 |
39 | ***BEFORE you start work on a feature/fix***, please read & follow our [Contributor's Guide](https://github.com/microsoft/WindowsAdvancedSettings/blob/main/CONTRIBUTING.md) to help avoid any wasted or duplicate effort.
40 |
41 | ## Communicating with the team
42 |
43 | The easiest way to communicate with the team is via GitHub issues.
44 |
45 | Please file new issues, feature requests and suggestions, but **DO search for similar open/closed preexisting issues before creating a new issue.**
46 |
47 | If you would like to ask a question that you feel doesn't warrant an issue (yet), please reach out to us via Twitter:
48 |
49 | * Kayla Cinnamon, Senior Product Manager: [@cinnamon_msft](https://twitter.com/cinnamon_msft)
50 | * Clint Rutkas, Principal Product Manager: [@clintrutkas](https://twitter.com/clintrutkas)
51 |
52 | ## Developer guidance
53 |
54 | * You must be running Windows 11 21H2 (build >= 10.0.22000.0) to run Dev Home
55 | * You must [enable Developer Mode in the Windows Settings app](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development)
56 |
57 | ## Building the code
58 |
59 | * Clone the repository
60 | * Uninstall the Preview version of the Windows Advanced Settings (Dev Home has a hard time choosing which extension to use if two versions exist)
61 | * Open `WindowsAdvancedSettings.sln` in Visual Studio 2022 or later and build from the IDE, or run `build\scripts\build.ps1` from a Visual Studio command prompt.
62 |
63 | ## Code of conduct
64 |
65 | We welcome contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
66 |
67 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
68 |
69 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
70 |
71 | ## Trademarks
72 |
73 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
74 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # Support
2 |
3 | ## How to file issues and get help
4 |
5 | For help and questions about using this project, please look at readme.
6 |
7 | ## Microsoft Support Policy
8 |
9 | Support for Windows Advanced Settings is limited to the resources listed above.
10 |
--------------------------------------------------------------------------------
/Solution.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(IntDir)Generated Files\
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TestingScenarios.md:
--------------------------------------------------------------------------------
1 | ## Testing Scenarios
2 |
3 | These are the testing scenarios that need to be validated before shipping a new release. When an automated test is added, please remove it from the below lists.
4 |
5 |
--------------------------------------------------------------------------------
/ToolingVersions.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | net8.0-windows10.0.22621.0
7 | 10.0.19041.0
8 | 10.0.19041.0
9 |
10 |
11 |
--------------------------------------------------------------------------------
/build/Build.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | powershell -ExecutionPolicy Unrestricted -NoLogo -NoProfile -File %~dp0\scripts\Build.ps1 %*
4 |
5 | exit /b %ERRORLEVEL%
--------------------------------------------------------------------------------
/build/EnsureOutputLayout.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 | true
9 |
10 |
11 | $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..'))
12 | $(RepoRoot)\build\
13 |
14 |
16 | $(Platform)
17 | x86
18 |
19 |
20 | $(RepoRoot)\BuildOutput\$(Configuration)\$(PlatformTarget)\
21 |
22 |
23 | $(RepoRoot)\obj\$(Configuration)\$(PlatformTarget)\$(MSBuildProjectName)\
24 | $(BaseIntermediateOutputPath)
25 |
26 |
27 | $(BaseOutputPath)
28 | $(OutDir)\$(MSBuildProjectName)\
29 | $(OutDir)
30 |
31 |
34 | $(BaseOutputPath)AppxPackages
35 | Never
36 |
37 |
--------------------------------------------------------------------------------
/build/SyncMirror-Steps.yml:
--------------------------------------------------------------------------------
1 | # This yml template is use to facilitate syncing public repositories to private mirrored repository.
2 | # These steps must be run on the target repository that is being synced.
3 |
4 | # Requirements:
5 | # The pipeline agent that runs this must have "Contribute" permissions on the target repository.
6 |
7 | parameters:
8 | - name: SourceRepository
9 | type: string
10 | default: ""
11 | - name: SourceBranch
12 | type: string
13 | default: ""
14 | - name: TargetBranch
15 | type: string
16 | default: ""
17 |
18 | steps:
19 | - checkout: self
20 | persistCredentials: true
21 |
22 | - task: powershell@2
23 | inputs:
24 | targetType: 'inline'
25 | script: |
26 | Write-Host "Sourcebranch " + "${{ parameters.SourceBranch }}"
27 | Write-Host "TargetBranch " + "${{ parameters.TargetBranch }}"
28 |
29 | $repo = "${{ parameters.SourceRepository }}"
30 | git remote add mirror $repo
31 | git remote
32 |
33 | $target = "${{ parameters.TargetBranch }}"
34 | git fetch origin $target
35 | git checkout $target
36 | git pull origin $target
37 |
38 | $source = "${{ parameters.SourceBranch }}"
39 | git fetch mirror $source
40 | git pull mirror $source
41 |
42 | - task: CmdLine@2
43 | inputs:
44 | script: |
45 | git push
46 |
--------------------------------------------------------------------------------
/build/SyncMirroredRepository.yml:
--------------------------------------------------------------------------------
1 | variables:
2 | DisableDockerDetector: true
3 | cfsNpmWarnLevel: 'warn'
4 | cfsCargoWarnLevel: 'warn'
5 |
6 | resources:
7 | repositories:
8 | - repository: m365Pipelines
9 | type: git
10 | name: 1ESPipelineTemplates/M365GPT
11 | ref: refs/tags/release
12 | extends:
13 | template: v1/M365.Official.PipelineTemplate.yml@m365Pipelines
14 | parameters:
15 | sdl:
16 | roslyn:
17 | enabled: true
18 | arrow:
19 | serviceConnection: DevHome Build VM Generation
20 | pool:
21 | name: Azure-Pipelines-1ESPT-ExDShared
22 | image: windows-2022
23 | os: windows
24 | customBuildTags:
25 | - ES365AIMigrationTooling
26 | stages:
27 | - stage: SyncMirror
28 | jobs:
29 | - job: SyncMirror
30 | dependsOn: []
31 | steps:
32 | - task: AzureKeyVault@1
33 | inputs:
34 | azureSubscription: 'DevHomeAzureServiceConnection'
35 | KeyVaultName: 'DevHomeKeyVault'
36 | SecretsFilter: 'GitHubPAT'
37 | RunAsPreJob: false
38 |
39 | - template: /build/SyncMirror-Steps.yml@self
40 | parameters:
41 | SourceRepository: "https://$(GitHubPAT)@github.com/microsoft/WindowsAdvancedSettings.git"
42 | TargetBranch: "$(SourceToTargetBranch)"
43 | SourceBranch: "$(SourceToTargetBranch)"
44 |
--------------------------------------------------------------------------------
/build/Test.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | powershell -ExecutionPolicy Unrestricted -NoLogo -NoProfile -File %~dp0\scripts\Test.ps1 %*
4 |
5 | exit /b %ERRORLEVEL%
--------------------------------------------------------------------------------
/build/TriggerReleaseBuild.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - release
3 |
4 | resources:
5 | repositories:
6 | - repository: templates_onebranch
7 | type: git
8 | name: OneBranch.Pipelines/GovernedTemplates
9 | ref: refs/heads/main
10 | - repository: m365Pipelines
11 | type: git
12 | name: 1ESPipelineTemplates/M365GPT
13 | ref: refs/tags/release
14 |
15 | extends:
16 | template: v1/M365.Official.PipelineTemplate.yml@m365Pipelines
17 | parameters:
18 | sdl:
19 | roslyn:
20 | enabled: true
21 | arrow:
22 | serviceConnection: WindowsAdvancedSettings Build VM Generation
23 | baseline:
24 | baselineFile: $(Build.SourcesDirectory)\guardian\SDL\.gdnbaselines
25 | pool:
26 | name: Azure-Pipelines-1ESPT-ExDShared
27 | image: windows-2022
28 | os: windows
29 | customBuildTags:
30 | - ES365AIMigrationTooling
31 | stages:
32 | - stage: Trigger_Build
33 | dependsOn: []
34 | jobs:
35 | - job: Trigger_Build
36 | steps:
37 | - script: echo Triggering ADO Build
38 | displayName: 'Triggering ADO Build'
--------------------------------------------------------------------------------
/build/TriggerStagingBuild.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - staging
3 |
4 | resources:
5 | repositories:
6 | - repository: templates_onebranch
7 | type: git
8 | name: OneBranch.Pipelines/GovernedTemplates
9 | ref: refs/heads/main
10 | - repository: m365Pipelines
11 | type: git
12 | name: 1ESPipelineTemplates/M365GPT
13 | ref: refs/tags/release
14 |
15 | extends:
16 | template: v1/M365.Official.PipelineTemplate.yml@m365Pipelines
17 | parameters:
18 | sdl:
19 | roslyn:
20 | enabled: true
21 | arrow:
22 | serviceConnection: WindowsAdvancedSettings Build VM Generation
23 | baseline:
24 | baselineFile: $(Build.SourcesDirectory)\guardian\SDL\.gdnbaselines
25 | pool:
26 | name: Azure-Pipelines-1ESPT-ExDShared
27 | image: windows-2022
28 | os: windows
29 | customBuildTags:
30 | - ES365AIMigrationTooling
31 | stages:
32 | - stage: Trigger_Build
33 | dependsOn: []
34 | jobs:
35 | - job: Trigger_Build
36 | steps:
37 | - script: echo Triggering ADO Build
38 | displayName: 'Triggering ADO Build'
--------------------------------------------------------------------------------
/build/cppversion/version.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | #define STRINGIZE2(s) #s
5 | #define STRINGIZE(s) STRINGIZE2(s)
6 |
7 | #define VERSION_MAJOR 1
8 | #define VERSION_MINOR 0
9 | #define VERSION_BUILD 0
10 | #define VERSION_REVISION 0
11 |
12 | #define VER_FILE_DESCRIPTION_STR "Dev Home"
13 | #define VER_ORIGINAL_FILENAME_STR "WindowsAdvancedSettings"
14 | #define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION
15 | #define VER_FILE_VERSION_STR STRINGIZE(VERSION_MAJOR) \
16 | "." STRINGIZE(VERSION_MINOR) \
17 | "." STRINGIZE(VERSION_BUILD) \
18 | "." STRINGIZE(VERSION_REVISION) \
19 |
20 | #define VER_PRODUCTNAME_STR "Dev Home"
21 | #define VER_PRODUCT_VERSION VER_FILE_VERSION
22 | #define VER_PRODUCT_VERSION_STR VER_FILE_VERSION_STR
23 | #define VER_INTERNAL_NAME_STR VER_ORIGINAL_FILENAME_STR
24 | #define VER_COPYRIGHT_STR "Copyright (c) Microsoft Corporation"
25 |
26 | #ifdef _DEBUG
27 | #define VER_VER_DEBUG VS_FF_DEBUG
28 | #else
29 | #define VER_VER_DEBUG 0
30 | #endif
31 |
32 | #define VER_FILEOS VOS_NT_WINDOWS32
33 | #define VER_FILEFLAGS VER_VER_DEBUG
34 | #define VER_FILETYPE VFT_APP
35 |
36 |
--------------------------------------------------------------------------------
/build/cppversion/version.rc:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 | //
4 | #include "version.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 | #pragma code_page(1252)
22 |
23 | #ifdef APSTUDIO_INVOKED
24 | /////////////////////////////////////////////////////////////////////////////
25 | //
26 | // TEXTINCLUDE
27 | //
28 |
29 | 1 TEXTINCLUDE
30 | BEGIN
31 | "resource.h\0"
32 | END
33 |
34 | 2 TEXTINCLUDE
35 | BEGIN
36 | "#include ""winres.h""\r\n"
37 | "\0"
38 | END
39 |
40 | 3 TEXTINCLUDE
41 | BEGIN
42 | "\r\n"
43 | "\0"
44 | END
45 |
46 | #endif // APSTUDIO_INVOKED
47 |
48 |
49 | /////////////////////////////////////////////////////////////////////////////
50 | //
51 | // Version
52 | //
53 |
54 | VS_VERSION_INFO VERSIONINFO
55 | FILEVERSION VER_FILE_VERSION
56 | PRODUCTVERSION VER_PRODUCT_VERSION
57 | FILEFLAGSMASK 0x3fL
58 | FILEFLAGS VER_FILEFLAGS
59 | FILEOS VER_FILEOS
60 | FILETYPE VER_FILETYPE
61 | FILESUBTYPE 0x0L
62 | BEGIN
63 | BLOCK "StringFileInfo"
64 | BEGIN
65 | BLOCK "040904b0"
66 | BEGIN
67 | VALUE "FileDescription", VER_FILE_DESCRIPTION_STR "\0"
68 | VALUE "FileVersion", VER_FILE_VERSION_STR "\0"
69 | VALUE "InternalName", VER_INTERNAL_NAME_STR "\0"
70 | VALUE "LegalCopyright", VER_COPYRIGHT_STR "\0"
71 | VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR "\0"
72 | VALUE "ProductName", VER_PRODUCTNAME_STR
73 | VALUE "ProductVersion", VER_PRODUCT_VERSION_STR "\0"
74 | END
75 | END
76 | BLOCK "VarFileInfo"
77 | BEGIN
78 | VALUE "Translation", 0x409, 1200
79 | END
80 | END
81 |
82 | #endif // English (United States) resources
83 | /////////////////////////////////////////////////////////////////////////////
84 |
85 |
86 |
87 | #ifndef APSTUDIO_INVOKED
88 | /////////////////////////////////////////////////////////////////////////////
89 | //
90 | // Generated from the TEXTINCLUDE 3 resource.
91 | //
92 |
93 |
94 | /////////////////////////////////////////////////////////////////////////////
95 | #endif // not APSTUDIO_INVOKED
96 |
97 |
--------------------------------------------------------------------------------
/build/cppversion/version.vcxitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | {6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}
7 |
8 |
9 |
10 | %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)include
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/build/nuget.config.internal:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/build/scripts/CertSignAndInstall.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-SignPackage([string]$Path) {
2 | if (-not($Path)) {
3 | Write-Host "Path parameter cannot be empty"
4 | return
5 | }
6 |
7 | if (-not(Test-Path $Path -PathType Leaf)) {
8 | Write-Host $Path is not a valid file
9 | return
10 | }
11 |
12 | $certName = "Microsoft.Windows.WindowsAdvancedSettings"
13 | $cert = Get-ChildItem 'Cert:\CurrentUser\My' | Where-Object {$_.FriendlyName -match $certName} | Select-Object -First 1
14 |
15 | if ($cert) {
16 | $expiration = $cert.NotAfter
17 | $now = Get-Date
18 | if ( $expiration -lt $now)
19 | {
20 | Write-Host "Test certificate for $($cert.Thumbprint)...Expired ($expiration). Replacing it with a new one."
21 | Remove-Item $cert
22 | $cert = $Null
23 | }
24 | }
25 |
26 | if (-not($cert)) {
27 | Write-Host "No certificate found. Creating a new certificate for signing."
28 | $cert = & New-SelfSignedCertificate -Type Custom -Subject "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" -KeyUsage DigitalSignature -FriendlyName $certName -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
29 | }
30 |
31 | SignTool sign /fd SHA256 /sha1 $($cert.Thumbprint) $Path
32 |
33 | if (-not(Test-Path Cert:\LocalMachine\TrustedPeople\$($cert.Thumbprint))) {
34 | Export-Certificate -Cert $cert -FilePath "$($PSScriptRoot)\Microsoft.Windows.WindowsAdvancedSettings.cer" -Type CERT
35 | Import-Certificate -FilePath "$($PSScriptRoot)\Microsoft.Windows.WindowsAdvancedSettings.cer" -CertStoreLocation Cert:\LocalMachine\TrustedPeople
36 | Remove-Item -Path "$($PSScriptRoot)\Microsoft.Windows.WindowsAdvancedSettings.cer"
37 | (Get-ChildItem Cert:\LocalMachine\TrustedPeople\$($cert.Thumbprint)).FriendlyName = $certName
38 | }
39 | }
40 |
41 | function Remove-WindowsAdvancedSettingsCertificates() {
42 | Get-ChildItem 'Cert:\CurrentUser\My' | Where-Object {$_.FriendlyName -match 'Microsoft.Windows.WindowsAdvancedSettings'} | Remove-Item
43 | Get-ChildItem 'Cert:\LocalMachine\TrustedPeople' | Where-Object {$_.FriendlyName -match 'Microsoft.Windows.WindowsAdvancedSettings'} | Remove-Item
44 | }
--------------------------------------------------------------------------------
/build/scripts/Create-AppxBundle.ps1:
--------------------------------------------------------------------------------
1 | Param(
2 | [Parameter(Mandatory,
3 | HelpMessage="Base name for input .appx files")]
4 | [string]
5 | $ProjectName,
6 |
7 | [Parameter(Mandatory,
8 | HelpMessage="Appx Bundle Version")]
9 | [version]
10 | $BundleVersion,
11 |
12 | [Parameter(Mandatory,
13 | HelpMessage="Path under which to locate appx/msix files")]
14 | [string]
15 | $InputPath,
16 |
17 | [Parameter(Mandatory,
18 | HelpMessage="Output Path")]
19 | [string]
20 | $OutputPath,
21 |
22 | [Parameter(HelpMessage="Path to makeappx.exe")]
23 | [ValidateScript({Test-Path $_ -Type Leaf})]
24 | [string]
25 | $MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x86\MakeAppx.exe"
26 | )
27 |
28 | If ($null -Eq (Get-Item $MakeAppxPath -EA:SilentlyContinue)) {
29 | Write-Error "Could not find MakeAppx.exe at `"$MakeAppxPath`".`nMake sure that -MakeAppxPath points to a valid SDK."
30 | Exit 1
31 | }
32 |
33 | # Enumerates a set of appx files beginning with a project name
34 | # and generates a temporary file containing a bundle content map.
35 | Function Create-AppxBundleMapping {
36 | Param(
37 | [Parameter(Mandatory)]
38 | [string]
39 | $InputPath,
40 |
41 | [Parameter(Mandatory)]
42 | [string]
43 | $ProjectName
44 | )
45 |
46 | $lines = @("[Files]")
47 | # Get-ChildItem -Path:$InputPath -Recurse -Filter:*$ProjectName* -Include *.appx, *.msix | % {
48 | Get-ChildItem -Path:$InputPath -Recurse -Include *.appx, *.msix | % {
49 | if ($_.FullName.Contains("\Stub\")) {
50 | $lines += ("`"{0}`" `"AppxMetadata\Stub\{1}`"" -f ($_.FullName, $_.Name))
51 | } else {
52 | $lines += ("`"{0}`" `"{1}`"" -f ($_.FullName, $_.Name))
53 | }
54 | }
55 |
56 | $outputFile = New-TemporaryFile
57 | $lines | Out-File -Encoding:ASCII $outputFile
58 | $outputFile
59 | }
60 |
61 | $NewMapping = Create-AppxBundleMapping -InputPath:$InputPath -ProjectName:$ProjectName
62 |
63 | & $MakeAppxPath bundle /v /bv $BundleVersion.ToString() /f $NewMapping.FullName /p $OutputPath
64 |
--------------------------------------------------------------------------------
/build/scripts/CreateBuildInfo.ps1:
--------------------------------------------------------------------------------
1 | [CmdLetBinding()]
2 | Param(
3 | [string]$Version,
4 | [bool]$IsAzurePipelineBuild = $false
5 | )
6 |
7 | $Major = "0"
8 | $Minor = "21"
9 | $Patch = "99" # default to 99 for local builds
10 |
11 | $versionSplit = $Version.Split(".");
12 | if ($versionSplit.length -gt 3) { $Build = $versionSplit[3] }
13 | if ($versionSplit.length -gt 2) { $Elapsed = $versionSplit[2] }
14 | if ($versionSplit.length -gt 1) {
15 | if ($versionSplit[1].length -gt 2) {
16 | $Minor = $versionSplit[1].SubString(0,$versionSplit[1].length-2);
17 | $Patch = $versionSplit[1].SubString($versionSplit[1].length-2, 2);
18 | } else {
19 | $Minor = $versionSplit[1]
20 | }
21 | }
22 | if ($versionSplit.length -gt 0) { $Major = $versionSplit[0] }
23 |
24 | # Compute/Verify the MSIX version
25 | #
26 | # MSIX Version = M.NPP.E.B
27 | # where
28 | # M = Major (max <= 65535)
29 | # N = Minor (max <= 654)
30 | # P = Patch (max <= 99)
31 | # E = Elapsed (max <= 65535)
32 | # B = Build (max <= 65535)
33 | #
34 | # NOTE: Elapsed is the number of days since the epoch (Jan'1, 2023).
35 | # NOTE: Make sure to compute Elapsed using Universal Time (UTC).
36 | #
37 | # NOTE: Build is computed as HHMM i.e. the time (hour and minute) when building locally, 0 otherwise.
38 | #
39 | $epoch = (Get-Date -Year 2023 -Month 1 -Day 1).ToUniversalTime()
40 | $now = (Get-Date).ToUniversalTime()
41 | if ([string]::IsNullOrWhiteSpace($Elapsed)) {
42 | $Elapsed = $(New-Timespan -Start $epoch -End $now).Days
43 | }
44 | #
45 | $version_h = $now.Hour
46 | $version_m = $now.Minute
47 | if (-not($IsAzurePipelineBuild) -And [string]::IsNullOrWhiteSpace($Build)) {
48 | $Build = ($version_h * 100 + $version_m).ToString()
49 | }
50 | #
51 | $version_dotquad = [int[]]($Major, ($Minor + $Patch), $Elapsed, $Build)
52 | return ($version_dotquad -Join ".")
--------------------------------------------------------------------------------
/build/scripts/Test.ps1:
--------------------------------------------------------------------------------
1 | Param(
2 | [string]$Platform = "x64",
3 | [string]$Configuration = "debug",
4 | [switch]$IsAzurePipelineBuild = $false,
5 | [switch]$Help = $false
6 | )
7 |
8 | $StartTime = Get-Date
9 |
10 | if ($Help) {
11 | Write-Host @"
12 | Copyright (c) Microsoft Corporation.
13 | Licensed under the MIT License.
14 |
15 | Syntax:
16 | Test.cmd [options]
17 |
18 | Description:
19 | Runs WindowsAdvancedSettings tests.
20 |
21 | Options:
22 |
23 | -Platform
24 | Only buil the selected platform(s)
25 | Example: -Platform x64
26 | Example: -Platform "x86,x64,arm64"
27 |
28 | -Configuration
29 | Only build the selected configuration(s)
30 | Example: -Configuration release
31 | Example: -Configuration "debug,release"
32 |
33 | -Help
34 | Display this usage message.
35 | "@
36 | Exit
37 | }
38 |
39 | # Root is two levels up from the script location.
40 | $env:Build_SourcesDirectory = (Get-Item $PSScriptRoot).parent.parent.FullName
41 | $env:Build_Platform = $Platform.ToLower()
42 | $env:Build_Configuration = $Configuration.ToLower()
43 |
44 | $vstestPath = &"${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -find **\TestPlatform\vstest.console.exe
45 |
46 | $ErrorActionPreference = "Stop"
47 |
48 | $isInstalled = Get-ChildItem HKLM:\SOFTWARE\$_\Microsoft\Windows\CurrentVersion\Uninstall\ | ? {($_.GetValue("DisplayName")) -like "*Windows Application Driver*"}
49 |
50 | if (-not($IsAzurePipelineBuild)) {
51 | if ($isInstalled){
52 | Write-Host "WinAppDriver is already installed on this computer."
53 | }
54 | else {
55 | Write-Host "WinAppDriver will be installed in the background."
56 | $url = "https://github.com/microsoft/WinAppDriver/releases/download/v1.2.99/WindowsApplicationDriver-1.2.99-win-x64.exe"
57 | $outpath = "$env:Build_SourcesDirectory\temp"
58 | Invoke-WebRequest -Uri $url -OutFile "$env:Build_SourcesDirectory\temp\WinAppDriverx64.exe"
59 |
60 | Start-Process -Wait -Filepath $env:Build_SourcesDirectory\WinAppDriverx64.exe -ArgumentList "/S" -PassThru
61 | }
62 |
63 | start-Process -FilePath "C:\Program Files\Windows Application Driver\WinAppDriver.exe"
64 | }
65 |
66 | Try {
67 | foreach ($platform in $env:Build_Platform.Split(",")) {
68 | foreach ($configuration in $env:Build_Configuration.Split(",")) {
69 | # TODO: UI tests are currently disabled in pipeline until signing is solved
70 | if (-not($IsAzurePipelineBuild)) {
71 | $Package = Get-AppPackage "WindowsAdvancedSettings"
72 | if ($Package) {
73 | Write-Host "Uninstalling old WindowsAdvancedSettings"
74 | Remove-AppPackage -Package $Package.PackageFullName
75 | }
76 | Write-Host "Installing WindowsAdvancedSettings"
77 | Add-AppPackage "AppxPackages\$platform\$configuration\WindowsAdvancedSettings.msix"
78 | }
79 | }
80 | }
81 | } Catch {
82 | $formatString = "`n{0}`n`n{1}`n`n"
83 | $fields = $_, $_.ScriptStackTrace
84 | Write-Host ($formatString -f $fields) -ForegroundColor RED
85 | Exit 1
86 | }
87 |
88 | if (-not($IsAzurePipelineBuild)) {
89 | Stop-Process -Name "WinAppDriver"
90 | }
91 |
92 | $TotalTime = (Get-Date)-$StartTime
93 | $TotalMinutes = [math]::Floor($TotalTime.TotalMinutes)
94 | $TotalSeconds = [math]::Ceiling($TotalTime.TotalSeconds)
95 |
96 | Write-Host @"
97 |
98 | Total Running Time:
99 | $TotalMinutes minutes and $TotalSeconds seconds
100 | "@ -ForegroundColor CYAN
--------------------------------------------------------------------------------
/build/scripts/UnbundleStubPackage.ps1:
--------------------------------------------------------------------------------
1 | param(
2 | [Parameter(Mandatory)]
3 | [string]
4 | $InputPath,
5 |
6 | [Parameter(Mandatory)]
7 | [string]
8 | $OutputLocation,
9 |
10 | [Parameter(HelpMessage="Path to makeappx.exe")]
11 | [ValidateScript({Test-Path $_ -Type Leaf})]
12 | [string]
13 | $MakeAppxPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe"
14 | )
15 |
16 | function Unbundle-AppxBundle
17 | {
18 | param($tool, $bundlePath, $toPath)
19 |
20 | $makeAppxCmd = ("& '" + $tool + "' unbundle /p " + $bundlePath + " /d '" + $toPath + "'")
21 |
22 | Write-Host $makeAppxCmd
23 | Invoke-Expression $makeAppxCmd
24 | }
25 |
26 | # Unbundle stub appxbundle
27 | Write-Host ("Input folder:" + $InputPath)
28 | $stubBundles = Get-ChildItem $InputPath -recurse | Where-Object {$_.extension -eq ".msixbundle"}
29 | if ($stubBundles.count -ne 1)
30 | {
31 | Write-Host -ForegroundColor RED $stubBundles.count + " stub appxbundle bundles found"
32 | exit 1
33 | }
34 |
35 | $stubBundle = $stubBundles | Select-Object -First 1
36 | Write-Host("Stub bundle path:" + $stubBundle.FullName)
37 | Unbundle-AppxBundle $MakeAppxPath $stubBundle.FullName $OutputLocation
--------------------------------------------------------------------------------
/build/scripts/Unstub.ps1:
--------------------------------------------------------------------------------
1 | # This script unstubs the telemetry at build time and replaces the stubbed file with a reference internal nuget package
2 |
3 | Remove-Item "$($PSScriptRoot)\..\..\src\Telemetry\TelemetryEventSource.cs"
4 |
5 | $projFile = "$($PSScriptRoot)\..\..\src\Telemetry\WindowsAdvancedSettings.Telemetry.csproj"
6 | $projFileContent = Get-Content $projFile -Encoding UTF8 -Raw
7 |
8 | if ($projFileContent.Contains('Microsoft.Telemetry.Inbox.Managed')) {
9 | Write-Output "Project file already contains a reference to the internal package."
10 | return;
11 | }
12 |
13 | $xml = [System.Xml.XmlDocument]$projFileContent
14 | $xml.PreserveWhitespace = $true
15 | $itemGroup = $xml.CreateElement("ItemGroup")
16 | $packageRef = $xml.CreateElement("PackageReference")
17 | $packageRef.SetAttribute("Include", "Microsoft.Telemetry.Inbox.Managed")
18 | $packageRef.SetAttribute("Version", "10.0.25148.1001-220626-1600.rs-fun-deploy-dev5")
19 | $itemGroup.AppendChild($packageRef)
20 | $xml.Project.AppendChild($itemGroup)
21 | $xml.Save($projFile)
--------------------------------------------------------------------------------
/build/store/SBConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "helpUri": "https:\\\\aka.ms\\StoreBroker_Config",
3 | "schemaVersion": 2,
4 | "packageParameters": {
5 | "PDPRootPath": "",
6 | "Release": "",
7 | "PDPInclude": [],
8 | "PDPExclude": [],
9 | "LanguageExclude": [
10 | "default",
11 | "qps-ploc",
12 | "qps-ploca",
13 | "qps-plocm"
14 | ],
15 | "MediaRootPath": "",
16 | "MediaFallbackLanguage": "",
17 | "PackagePath": [],
18 | "OutPath": "",
19 | "OutName": "",
20 | "DisableAutoPackageNameFormatting": false
21 | },
22 | "appSubmission": {
23 | "productId": "00014046768474458793",
24 | "targetPublishMode": "NotSet",
25 | "targetPublishDate": null,
26 | "visibility": "NotSet",
27 | "pricing": {
28 | "priceId": "NotAvailable",
29 | "trialPeriod": "NoFreeTrial",
30 | "marketSpecificPricings": {},
31 | "sales": []
32 | },
33 | "allowTargetFutureDeviceFamilies": {
34 | "Xbox": false,
35 | "Team": false,
36 | "Holographic": false,
37 | "Desktop": false,
38 | "Mobile": false
39 | },
40 | "allowMicrosoftDecideAppAvailabilityToFutureDeviceFamilies": false,
41 | "enterpriseLicensing": "None",
42 | "applicationCategory": "NotSet",
43 | "hardwarePreferences": [],
44 | "hasExternalInAppProducts": false,
45 | "meetAccessibilityGuidelines": false,
46 | "canInstallOnRemovableMedia": false,
47 | "automaticBackupEnabled": false,
48 | "isGameDvrEnabled": false,
49 | "gamingOptions": [
50 | {
51 | "genres": [],
52 | "isLocalMultiplayer": false,
53 | "isLocalCooperative": false,
54 | "isOnlineMultiplayer": false,
55 | "isOnlineCooperative": false,
56 | "localMultiplayerMinPlayers": 0,
57 | "localMultiplayerMaxPlayers": 0,
58 | "localCooperativeMinPlayers": 0,
59 | "localCooperativeMaxPlayers": 0,
60 | "isBroadcastingPrivilegeGranted": false,
61 | "isCrossPlayEnabled": false,
62 | "kinectDataForExternal": "Disabled"
63 | }
64 | ],
65 | "notesForCertification": ""
66 | }
67 | }
--------------------------------------------------------------------------------
/build/store/media/de-DE/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/de-DE/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/de-DE/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/de-DE/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/en-us/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/en-us/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/en-us/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/en-us/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/es-ES/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/es-ES/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/es-ES/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/es-ES/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/fr-FR/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/fr-FR/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/fr-FR/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/fr-FR/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/it-IT/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/it-IT/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/it-IT/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/it-IT/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/ja-JP/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/ja-JP/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/ja-JP/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/ja-JP/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/ko-KR/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/ko-KR/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/ko-KR/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/ko-KR/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/pt-BR/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/pt-BR/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/pt-BR/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/pt-BR/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/ru-RU/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/ru-RU/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/ru-RU/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/ru-RU/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/zh-CN/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/zh-CN/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/zh-CN/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/zh-CN/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/store/media/zh-TW/AdvancedSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/zh-TW/AdvancedSettings.png
--------------------------------------------------------------------------------
/build/store/media/zh-TW/FileExplorerSourceIntegration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/build/store/media/zh-TW/FileExplorerSourceIntegration.png
--------------------------------------------------------------------------------
/build/templates/EsrpSigning-Steps.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | - name: displayName
3 | type: string
4 | default: ESRP Code Signing
5 | - name: inputs
6 | type: object
7 | default: {}
8 |
9 | steps:
10 | - task: EsrpCodeSigning@5
11 | displayName: ${{ parameters.displayName }}
12 | inputs:
13 | ConnectedServiceName: $(EsrpConnectedServiceName)
14 | UseMSIAuthentication: true
15 | AppRegistrationClientId: $(EsrpAppRegistrationClientId)
16 | AppRegistrationTenantId: $(EsrpAppRegistrationTenantId)
17 | EsrpClientId: $(EsrpClientId)
18 | AuthAKVName: $(EsrpAuthAKVName)
19 | AuthCertName: $(EsrpAuthCertName)
20 | AuthSignCertName: $(EsrpAuthSignCertName)
21 | SessionTimeout: '60'
22 | MaxConcurrency: '50'
23 | MaxRetryAttempts: '5'
24 | ${{ insert }}: ${{ parameters.inputs }}
--------------------------------------------------------------------------------
/build/templates/publish-symbolrequestprod-api.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | - name: includePublicSymbolServer
3 | type: boolean
4 | default: false
5 | - name: symbolsFolder
6 | type: string
7 | default: '$(Build.SourcesDirectory)/bin'
8 | - name: searchPattern
9 | type: string
10 | default: '**/*.pdb'
11 | - name: jobName
12 | type: string
13 | default: PublishSymbols
14 | - name: indexSources
15 | type: boolean
16 | default: true
17 | - name: symbolExpiryTime
18 | type: string
19 | default: 36530 # This is the default from PublishSymbols@2
20 | - name: symbolsArtifactName
21 | type: string
22 | default: ''
23 | - name: symbolsVersion
24 | type: string
25 | default: ''
26 | - name: symbolProject
27 | type: string
28 | - name: subscription
29 | type: string
30 |
31 | steps:
32 | - powershell: |-
33 | Get-PackageProvider -Name NuGet -ForceBootstrap
34 | Install-Module -Verbose -AllowClobber -Force Az.Accounts, Az.Storage, Az.Network, Az.Resources, Az.Compute
35 | displayName: Install Azure Module Dependencies
36 |
37 | # Transit the Azure token from the Service Connection into a secret variable for the rest of the pipeline to use.
38 | - task: AzurePowerShell@5
39 | displayName: Generate an Azure Token
40 | inputs:
41 | azureSubscription: ${{ parameters.subscription }}
42 | azurePowerShellVersion: LatestVersion
43 | pwsh: true
44 | ScriptType: InlineScript
45 | Inline: |-
46 | $AzToken = (Get-AzAccessToken -ResourceUrl api://30471ccf-0966-45b9-a979-065dbedb24c1).Token
47 | Write-Host "##vso[task.setvariable variable=SymbolAccessToken;issecret=true]$AzToken"
48 |
49 | - task: PublishSymbols@2
50 | displayName: Publish Symbols (to current Azure DevOps tenant)
51 | continueOnError: True
52 | inputs:
53 | SearchPattern: ${{ parameters.searchPattern }}
54 | IndexSources: ${{ parameters.indexSources }}
55 | DetailedLog: true
56 | SymbolsMaximumWaitTime: 30
57 | SymbolServerType: 'TeamServices'
58 | SymbolsProduct: 'DevHome'
59 | SymbolsVersion: ${{ parameters.symbolsVersion }}
60 | SymbolsArtifactName: '${{ parameters.symbolsArtifactName }}_${{ parameters.symbolsVersion }}'
61 | SymbolExpirationInDays: ${{ parameters.symbolExpiryTime }}
62 | env:
63 | LIB: $(Build.SourcesDirectory)
64 |
65 | - pwsh: |-
66 | # Prepare the defaults for IRM
67 | $PSDefaultParameterValues['Invoke-RestMethod:Headers'] = @{ Authorization = "Bearer $(SymbolAccessToken)" }
68 | $PSDefaultParameterValues['Invoke-RestMethod:ContentType'] = "application/json"
69 | $PSDefaultParameterValues['Invoke-RestMethod:Method'] = "POST"
70 |
71 | $BaseUri = "https://symbolrequestprod.trafficmanager.net/projects/${{ parameters.symbolProject }}/requests"
72 |
73 | # Prepare the request
74 | $expiration = (Get-Date).Add([TimeSpan]::FromDays(${{ parameters.symbolExpiryTime }}))
75 | $createRequestBody = @{
76 | requestName = "${{ parameters.symbolsArtifactName }}_${{ parameters.symbolsVersion }}";
77 | expirationTime = $expiration.ToString();
78 | }
79 | Write-Host "##[debug]Starting request $($createRequestBody.requestName) with expiration date of $($createRequestBody.expirationTime)"
80 | Invoke-RestMethod -Uri "$BaseUri" -Body ($createRequestBody | ConvertTo-Json -Compress) -Verbose
81 |
82 | # Request symbol publication
83 | $publishRequestBody = @{
84 | publishToInternalServer = $true;
85 | publishToPublicServer = $${{ parameters.includePublicSymbolServer }};
86 | }
87 | Write-Host "##[debug]Submitting request $($createRequestBody.requestName) ($($publishRequestBody | ConvertTo-Json -Compress))"
88 | Invoke-RestMethod -Uri "$BaseUri/$($createRequestBody.requestName)" -Body ($publishRequestBody | ConvertTo-Json -Compress) -Verbose
89 | displayName: Publish Symbols using internal REST API
--------------------------------------------------------------------------------
/codeAnalysis/Rules.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/codeAnalysis/StyleCop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "Microsoft Corporation",
6 | "copyrightText": "Copyright (c) {companyName}.\r\nLicensed under the MIT License.",
7 | "xmlHeader": false,
8 | "headerDecoration": "",
9 | "fileNamingConvention": "metadata",
10 | "documentInterfaces": false,
11 | "documentExposedElements": false,
12 | "documentInternalElements": false
13 | },
14 | "layoutRules": {
15 | "newlineAtEndOfFile": "require"
16 | },
17 | "orderingRules": {
18 | "usingDirectivesPlacement": "outsideNamespace"
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/codeAnalysis/format_sources.ps1:
--------------------------------------------------------------------------------
1 | param (
2 | [switch]$all = $false
3 | )
4 |
5 | if(!(Get-Command "git" -ErrorAction SilentlyContinue)) {
6 | throw "You need to have a git in path to be able to format only the dirty files!"
7 | }
8 |
9 | $clangFormat = "clang-format.exe"
10 | if(!(Get-Command $clangFormat -ErrorAction SilentlyContinue)) {
11 | Write-Information "Can't find clang-format.exe in %PATH%, trying to use %VCINSTALLDIR%..."
12 | $clangFormat="$env:VCINSTALLDIR\Tools\Llvm\bin\clang-format.exe"
13 | if(!(Test-Path -Path $clangFormat -PathType leaf)) {
14 | throw "Can't find clang-format.exe executable. Make sure you either have it in %PATH% or run this script from vcvars.bat!"
15 | }
16 | }
17 |
18 | $sourceExtensions = New-Object System.Collections.Generic.HashSet[string]
19 | $sourceExtensions.Add(".cpp") | Out-Null
20 | $sourceExtensions.Add(".h") | Out-Null
21 |
22 | function Get-Dirty-Files-From-Git() {
23 | $repo_root = & git rev-parse --show-toplevel
24 |
25 | $staged = & git diff --name-only --diff-filter=d --cached | % { $repo_root + "/" + $_ }
26 | $unstaged = & git ls-files -m
27 | $untracked = & git ls-files --others --exclude-standard
28 | $result = New-Object System.Collections.Generic.List[string]
29 | $staged, $unstaged, $untracked | % {
30 | $_.Split(" ") |
31 | where {Test-Path $_ -PathType Leaf} |
32 | where {$sourceExtensions.Contains((Get-Item $_).Extension)} |
33 | foreach {$result.Add($_)}
34 | }
35 | return $result
36 | }
37 |
38 | if($all) {
39 | $filesToFormat =
40 | Get-ChildItem -Recurse -File ..\src |
41 | Resolve-Path -Relative |
42 | where { (Get-Item $_).Directory -notmatch "(Generated Files)|node_modules" -And
43 | $sourceExtensions.Contains((Get-Item $_).Extension)}
44 | }
45 | else {
46 | $filesToFormat = Get-Dirty-Files-From-Git
47 | }
48 |
49 | $filesToFormat | % {
50 | Write-Host "Formatting $_"
51 | & $clangFormat -i -style=file -fallback-style=none $_ 2>&1
52 | }
53 |
54 | Write-Host "Done!"
--------------------------------------------------------------------------------
/exclusion.dic:
--------------------------------------------------------------------------------
1 | devhome
2 | Impl
3 | Msal
4 | visualstudio
5 | vssps
6 | setx
7 | oauth
8 | pullrequest
9 | Sqls
10 | microsoft
11 | brokerplugin
12 | Entra
13 | riid
14 | enums
15 | Stdout
16 | foobar
17 | msix
18 | yaml
19 | Quickstart
20 | Tensorflow
21 | vscode
22 | codespace
23 | codespaces
24 | dhlog
25 | appsettings
26 | deserializer
27 | winget
28 | csharp
29 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/AdvancedSettings.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/AdvancedSettings.ico
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/MedTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/MedTile.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/SmallTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/SmallTile.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/SplashScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/SplashScreen.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/StoreLogo.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-black.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-black_scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-black_scale-100.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-white.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-white_scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Dev/splashscreen.contrast-white_scale-100.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Fonts/AMCIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Fonts/AMCIcons.ttf
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Fonts/CascadiaMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Fonts/CascadiaMono.ttf
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Fonts/DevHome.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Fonts/DevHome.ttf
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Fonts/README.md:
--------------------------------------------------------------------------------
1 | # Included fonts
2 | - Cascadia Mono (v2111.01)
3 | - https://github.com/microsoft/cascadia-code/releases/tag/v2111.01
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/InitializationPage/AppList.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/InitializationPage/AppList.scale-400.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/MedTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/MedTile.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/SmallTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/SmallTile.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/SplashScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/SplashScreen.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/StoreLogo.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/splashscreen.contrast-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/splashscreen.contrast-black.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/splashscreen.contrast-black_scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/splashscreen.contrast-black_scale-100.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/splashscreen.contrast-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/splashscreen.contrast-white.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/Assets/Production/splashscreen.contrast-white_scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/src/AdvancedSettings/Assets/Production/splashscreen.contrast-white_scale-100.png
--------------------------------------------------------------------------------
/src/AdvancedSettings/NativeMethods.txt:
--------------------------------------------------------------------------------
1 | CoRegisterClassObject
2 | CoRevokeClassObject
3 | CoResumeClassObjects
4 | MEMORYSTATUSEX
5 | GlobalMemoryStatusEx
6 | SHChangeNotify
7 | SHLoadIndirectString
8 | GetCurrentPackageFullName
9 | OpenProcessToken
10 | GetTokenInformation
11 | GetCurrentProcess
12 | TOKEN_ELEVATION_TYPE
13 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/Package-Dev.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Windows Advanced Settings (Dev)
6 | Microsoft Corporation
7 | Assets\Logos\StoreLogo.png
8 | disabled
9 | disabled
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Windows Advanced Settings (Dev)
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/Package.appinstaller:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Windows Advanced Settings
6 | Microsoft Corporation
7 | Assets\Logos\StoreLogo.png
8 | disabled
9 | disabled
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Windows Advanced Settings
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using FileExplorerSourceControlIntegration;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer;
7 | using Microsoft.Windows.AppLifecycle;
8 | using Serilog;
9 | using WindowsAdvancedSettings.Common.Helpers;
10 |
11 | namespace WindowsAdvancedSettings;
12 |
13 | public sealed class Program
14 | {
15 | [MTAThread]
16 | public static async Task Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
17 | {
18 | // Set up Logging
19 | Environment.SetEnvironmentVariable("WINDOWSADVANCEDSETTINGS_LOG_ROOT", Path.Join(Logging.LogFolderRoot, "WindowsAdvancedSettings"));
20 | var configuration = new ConfigurationBuilder()
21 | .AddJsonFile("appsettings.json")
22 | .Build();
23 | Log.Logger = new LoggerConfiguration()
24 | .ReadFrom.Configuration(configuration)
25 | .CreateLogger();
26 |
27 | Log.Information($"Launched with args: {string.Join(' ', args.ToArray())}");
28 |
29 | // Force the app to be single instanced
30 | // Get or register the main instance
31 | var mainInstance = AppInstance.FindOrRegisterForKey("mainInstance");
32 | var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
33 |
34 | // If the main instance isn't this current instance
35 | if (!mainInstance.IsCurrent)
36 | {
37 | Log.Information($"Not main instance, redirecting.");
38 | await mainInstance.RedirectActivationToAsync(activationArgs);
39 | Log.CloseAndFlush();
40 | return;
41 | }
42 |
43 | // Otherwise, we're in the main instance
44 | // Register for activation redirection
45 | AppInstance.GetCurrent().Activated += AppActivationRedirected;
46 |
47 | Setup.ConfigureRegistry();
48 | Log.CloseAndFlush();
49 | #if DEBUG
50 | Console.ReadLine();
51 | #endif
52 | }
53 |
54 | private static void AppActivationRedirected(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments activationArgs)
55 | {
56 | Log.Information($"Redirected with kind: {activationArgs.Kind}");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/Properties/launchsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Windows Advanced Settings (Package)": {
4 | "commandName": "MsixPackage",
5 | "doNotLaunchApp": false,
6 | "nativeDebugging": false
7 | },
8 | "Windows Advanced Settings (Unpackaged)": {
9 | "commandName": "Project"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/AdvancedSettings/Setup.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using FileExplorerSourceControlIntegration;
5 | using Microsoft.Win32;
6 | using Serilog;
7 |
8 | namespace WindowsAdvancedSettings;
9 |
10 | public static class Setup
11 | {
12 | private static readonly string _advancedSettingsRegistryKeyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\AdvancedSettings";
13 | private static readonly string _sourceControlProviderValueName = @"SourceControlProvider";
14 | private static readonly string _protocolLaunchValueName = @"Protocol";
15 |
16 | #if DEBUG
17 | private static readonly string _protocolLaunchValue = @"ms-advancedsettingsdev://";
18 | #else
19 | private static readonly string _protocolLaunchValue = @"ms-advancedsettings://";
20 | #endif
21 |
22 | public static void ConfigureRegistry()
23 | {
24 | try
25 | {
26 | Registry.CurrentUser.DeleteSubKey(_advancedSettingsRegistryKeyPath, false);
27 | using var key = Registry.CurrentUser.CreateSubKey(_advancedSettingsRegistryKeyPath, true);
28 | key.SetValue(_sourceControlProviderValueName, typeof(SourceControlProvider).GUID.ToString());
29 | key.SetValue(_protocolLaunchValueName, _protocolLaunchValue);
30 | key.Close();
31 | Log.Debug($"Registry Key under {_advancedSettingsRegistryKeyPath}");
32 | Log.Debug($" {_sourceControlProviderValueName} = {typeof(SourceControlProvider).GUID}");
33 | Log.Debug($" {_protocolLaunchValueName} = {_protocolLaunchValue}");
34 | }
35 | catch (Exception ex)
36 | {
37 | Log.Error(ex, $"Failed configuring the registry.");
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/TemplateStudio.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/WindowsAdvancedSettings.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Exe
11 |
12 |
13 | WinExe
14 |
15 |
16 |
17 | WindowsAdvancedSettings
18 | Dev
19 | Assets\AdvancedSettings.ico
20 | Assets\AdvancedSettings.ico
21 | app.manifest
22 | x86;x64;arm64
23 | win-x86;win-x64;win-arm64
24 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
25 | enable
26 | enable
27 | true
28 | true
29 | true
30 | true
31 | $(DefineConstants);DISABLE_XAML_GENERATED_MAIN
32 |
33 |
34 |
35 |
36 | Guard
37 | Spectre
38 |
39 |
40 |
41 |
42 |
43 | all
44 | runtime; build; native; contentfiles; analyzers; buildtransitive
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Designer
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Designer
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | Always
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | true
97 |
98 |
99 |
100 |
101 |
102 | PreserveNewest
103 | Assets\NOTICE.txt
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | $(DefineConstants);STABLE_BUILD
120 | net8.0-windows10.0.22621.0
121 |
122 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/AdvancedSettings/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ],
4 | "MinimumLevel": "Debug",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
10 | "restrictedToMinimumLevel": "Debug"
11 | }
12 | },
13 | {
14 | "Name": "File",
15 | "Args": {
16 | "path": "%WINDOWSADVANCEDSETTINGS_LOG_ROOT%\\FileExplorerGitIntegration.dhlog",
17 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
18 | "restrictedToMinimumLevel": "Information",
19 | "rollingInterval": "Day"
20 | }
21 | },
22 | {
23 | "Name": "Debug"
24 | }
25 | ],
26 | "Enrich": [ "FromLogContext" ],
27 | "Properties": {
28 | "SourceContext": "WindowsAdvancedSettings"
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Common/Helpers/DirectoryHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.IO;
6 | using System.Threading;
7 | using Serilog;
8 |
9 | namespace WindowsAdvancedSettings.Common.Helpers;
10 |
11 | public static class DirectoryHelper
12 | {
13 | private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(DirectoryHelper));
14 |
15 | // Attempt to delete a directory with retries and an increasing backoff delay between retry attempts.
16 | // This is useful when the directory may be temporarily in use by another process and the deletion may fail.
17 | public static void DeleteDirectoryWithRetries(string directoryPath, bool recursive = true, int maxRetries = 3, int initialRetryDelayMs = 100, bool throwOnFailure = true)
18 | {
19 | ArgumentException.ThrowIfNullOrEmpty(directoryPath);
20 | ArgumentOutOfRangeException.ThrowIfNegative(maxRetries);
21 | ArgumentOutOfRangeException.ThrowIfNegative(initialRetryDelayMs);
22 |
23 | var retryDelay = initialRetryDelayMs;
24 | for (var i = 0; i <= maxRetries; ++i)
25 | {
26 | try
27 | {
28 | if (Directory.Exists(directoryPath))
29 | {
30 | Directory.Delete(directoryPath, recursive);
31 | }
32 |
33 | return;
34 | }
35 | catch (Exception ex)
36 | {
37 | if (i == maxRetries)
38 | {
39 | _log.Error(ex, $"Failed to delete directory {directoryPath} on attempt {i + 1}.");
40 | if (throwOnFailure)
41 | {
42 | throw;
43 | }
44 | else
45 | {
46 | return;
47 | }
48 | }
49 | else
50 | {
51 | _log.Information(ex, $"Failed to delete directory {directoryPath} on attempt {i + 1}. Retrying up to {maxRetries - i} more times.");
52 | }
53 | }
54 |
55 | Thread.Sleep(retryDelay);
56 | retryDelay *= 2;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Common/Helpers/FileService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.IO;
5 | using System.Text;
6 | using System.Text.Json;
7 |
8 | namespace WindowsAdvancedSettings.Common.Helpers;
9 |
10 | public class FileService
11 | {
12 | #pragma warning disable CS8603 // Possible null reference return.
13 | public T Read(string folderPath, string fileName)
14 | {
15 | var path = Path.Combine(folderPath, fileName);
16 | if (File.Exists(path))
17 | {
18 | using var fileStream = File.OpenText(path);
19 | return JsonSerializer.Deserialize(fileStream.BaseStream);
20 | }
21 |
22 | return default;
23 | }
24 | #pragma warning restore CS8603 // Possible null reference return.
25 |
26 | public void Save(string folderPath, string fileName, T content)
27 | {
28 | if (!Directory.Exists(folderPath))
29 | {
30 | Directory.CreateDirectory(folderPath);
31 | }
32 |
33 | var fileContent = JsonSerializer.Serialize(content);
34 | File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8);
35 | }
36 |
37 | public void Delete(string folderPath, string fileName)
38 | {
39 | if (fileName != null && File.Exists(Path.Combine(folderPath, fileName)))
40 | {
41 | File.Delete(Path.Combine(folderPath, fileName));
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Common/Helpers/GitCommandRunnerResultInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using Microsoft.Windows.DevHome.SDK;
6 |
7 | namespace WindowsAdvancedSettings.Common.Helpers;
8 |
9 | public class GitCommandRunnerResultInfo
10 | {
11 | public ProviderOperationStatus Status { get; set; }
12 |
13 | public string? Output { get; set; }
14 |
15 | public string? DisplayMessage { get; set; }
16 |
17 | public string? DiagnosticText { get; set; }
18 |
19 | public Exception? Ex { get; set; }
20 |
21 | public string? Arguments { get; set; }
22 |
23 | public int? ProcessExitCode { get; set; }
24 |
25 | public GitCommandRunnerResultInfo(ProviderOperationStatus status, string? output)
26 | {
27 | Status = status;
28 | Output = output;
29 | }
30 |
31 | public GitCommandRunnerResultInfo(ProviderOperationStatus status, string? output, string? displayMessage, string? diagnosticText, Exception? ex, string? args, int? processExitCode)
32 | {
33 | Status = status;
34 | Output = output;
35 | DisplayMessage = displayMessage;
36 | DiagnosticText = diagnosticText;
37 | Ex = ex;
38 | Arguments = args;
39 | ProcessExitCode = processExitCode;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Common/Helpers/Json.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.IO;
5 | using System.Text;
6 | using System.Text.Json;
7 | using System.Threading.Tasks;
8 |
9 | namespace WindowsAdvancedSettings.Common.Helpers;
10 |
11 | public static class Json
12 | {
13 | public static async Task ToObjectAsync(string value)
14 | {
15 | if (typeof(T) == typeof(bool))
16 | {
17 | return (T)(object)bool.Parse(value);
18 | }
19 |
20 | await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(value));
21 | return (await JsonSerializer.DeserializeAsync(stream))!;
22 | }
23 |
24 | public static async Task StringifyAsync(T value)
25 | {
26 | if (typeof(T) == typeof(bool))
27 | {
28 | return value!.ToString()!.ToLowerInvariant();
29 | }
30 |
31 | await using var stream = new MemoryStream();
32 | await JsonSerializer.SerializeAsync(stream, value);
33 | return Encoding.UTF8.GetString(stream.ToArray());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Common/Helpers/Logging.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.IO;
6 | using Microsoft.Extensions.Configuration;
7 | using Serilog;
8 | using Windows.Storage;
9 |
10 | namespace WindowsAdvancedSettings.Common.Helpers;
11 |
12 | public class Logging
13 | {
14 | public static readonly string LogExtension = ".waslog";
15 |
16 | public static readonly string LogFolderName = "Logs";
17 |
18 | public static readonly string DefaultLogFileName = "was";
19 |
20 | private static readonly Lazy _logFolderRoot = new(() => Path.Combine(ApplicationData.Current.TemporaryFolder.Path, LogFolderName));
21 |
22 | public static readonly string LogFolderRoot = _logFolderRoot.Value;
23 |
24 | public static void SetupLogging(string jsonFileName, string appName)
25 | {
26 | Environment.SetEnvironmentVariable("WINDOWSADVANCEDSETTINGS_LOG_ROOT", Path.Join(LogFolderRoot, appName));
27 | var configuration = new ConfigurationBuilder()
28 | .AddJsonFile(jsonFileName)
29 | .Build();
30 | Log.Logger = new LoggerConfiguration()
31 | .ReadFrom.Configuration(configuration)
32 | .CreateLogger();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Common/Helpers/Resources.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using Microsoft.Windows.ApplicationModel.Resources;
6 | using Serilog;
7 |
8 | namespace WindowsAdvancedSettings.Common.Helpers;
9 |
10 | public static class Resources
11 | {
12 | private static ResourceLoader? _resourceLoader;
13 |
14 | public static string GetResource(string identifier, ILogger? log = null)
15 | {
16 | try
17 | {
18 | if (_resourceLoader == null)
19 | {
20 | if (RuntimeHelper.IsPackaged)
21 | {
22 | // Packaged resource map will be in the merged PRI.
23 | _resourceLoader = new ResourceLoader(ResourceLoader.GetDefaultResourceFilePath(), "Resources");
24 | }
25 | else
26 | {
27 | // Unpackaged will not be merged and will instead be in the named PRI.
28 | _resourceLoader = new ResourceLoader("FileExplorerGitIntegration.pri", "FileExplorerGitIntegration/Resources");
29 | }
30 | }
31 |
32 | return _resourceLoader.GetString(identifier);
33 | }
34 | catch (Exception ex)
35 | {
36 | log?.Error(ex, $"Failed loading resource: {identifier}");
37 |
38 | // If we fail, load the original identifier so it is obvious which resource is missing.
39 | return identifier;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Common/Helpers/RuntimeHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Windows.Win32;
5 | using Windows.Win32.Foundation;
6 |
7 | namespace WindowsAdvancedSettings.Common.Helpers;
8 |
9 | public static class RuntimeHelper
10 | {
11 | public static bool IsPackaged
12 | {
13 | get
14 | {
15 | uint length = 0;
16 | return PInvoke.GetCurrentPackageFullName(ref length, null) != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Common/Helpers/SettingsStorageExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.IO;
6 | using System.Threading.Tasks;
7 | using Windows.Storage;
8 | using Windows.Storage.Streams;
9 |
10 | namespace WindowsAdvancedSettings.Common.Helpers;
11 |
12 | // Use these extension methods to store and retrieve local and roaming app data
13 | // More details regarding storing and retrieving app data at https://docs.microsoft.com/windows/apps/design/app-settings/store-and-retrieve-app-data
14 | public static class SettingsStorageExtensions
15 | {
16 | private const string FileExtension = ".json";
17 |
18 | public static bool IsRoamingStorageAvailable(this ApplicationData appData)
19 | {
20 | return appData.RoamingStorageQuota == 0;
21 | }
22 |
23 | public static async Task SaveAsync(this StorageFolder folder, string name, T content)
24 | {
25 | var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting);
26 | var fileContent = await Json.StringifyAsync(content!);
27 |
28 | await FileIO.WriteTextAsync(file, fileContent);
29 | }
30 |
31 | public static async Task ReadAsync(this StorageFolder folder, string name)
32 | {
33 | if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
34 | {
35 | return default;
36 | }
37 |
38 | var file = await folder.GetFileAsync($"{name}.json");
39 | var fileContent = await FileIO.ReadTextAsync(file);
40 |
41 | return await Json.ToObjectAsync(fileContent);
42 | }
43 |
44 | public static async Task SaveAsync(this ApplicationDataContainer settings, string key, T value)
45 | {
46 | settings.SaveString(key, await Json.StringifyAsync(value!));
47 | }
48 |
49 | public static void SaveString(this ApplicationDataContainer settings, string key, string value)
50 | {
51 | settings.Values[key] = value;
52 | }
53 |
54 | public static async Task ReadAsync(this ApplicationDataContainer settings, string key)
55 | {
56 | object? obj;
57 |
58 | if (settings.Values.TryGetValue(key, out obj))
59 | {
60 | return await Json.ToObjectAsync((string)obj);
61 | }
62 |
63 | return default;
64 | }
65 |
66 | public static async Task SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting)
67 | {
68 | ArgumentNullException.ThrowIfNull(content);
69 |
70 | if (string.IsNullOrEmpty(fileName))
71 | {
72 | throw new ArgumentException("File name is null or empty. Specify a valid file name", nameof(fileName));
73 | }
74 |
75 | var storageFile = await folder.CreateFileAsync(fileName, options);
76 | await FileIO.WriteBytesAsync(storageFile, content);
77 | return storageFile;
78 | }
79 |
80 | public static async Task ReadFileAsync(this StorageFolder folder, string fileName)
81 | {
82 | var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
83 |
84 | if (item != null && item.IsOfType(StorageItemTypes.File))
85 | {
86 | var storageFile = await folder.GetFileAsync(fileName);
87 | var content = await storageFile.ReadBytesAsync();
88 | return content;
89 | }
90 |
91 | return null;
92 | }
93 |
94 | public static async Task ReadBytesAsync(this StorageFile file)
95 | {
96 | if (file != null)
97 | {
98 | using IRandomAccessStream stream = await file.OpenReadAsync();
99 | using var reader = new DataReader(stream.GetInputStreamAt(0));
100 | await reader.LoadAsync((uint)stream.Size);
101 | var bytes = new byte[stream.Size];
102 | reader.ReadBytes(bytes);
103 | return bytes;
104 | }
105 |
106 | return null;
107 | }
108 |
109 | private static string GetFileName(string name)
110 | {
111 | return string.Concat(name, FileExtension);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Common/NativeMethods.txt:
--------------------------------------------------------------------------------
1 | CoRegisterClassObject
2 | CoRevokeClassObject
3 | CoResumeClassObjects
4 | MEMORYSTATUSEX
5 | GlobalMemoryStatusEx
6 | SHChangeNotify
7 | SHLoadIndirectString
8 | GetCurrentPackageFullName
9 | OpenProcessToken
10 | GetTokenInformation
11 | GetCurrentProcess
12 | TOKEN_ELEVATION_TYPE
13 |
--------------------------------------------------------------------------------
/src/Common/PublishProfiles/win-arm64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | arm64
9 | win-arm64
10 | true
11 | False
12 | False
13 | True
14 | True
15 |
16 |
--------------------------------------------------------------------------------
/src/Common/PublishProfiles/win-x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | x64
9 | win-x64
10 | true
11 | False
12 | False
13 | True
14 | True
15 |
16 |
--------------------------------------------------------------------------------
/src/Common/PublishProfiles/win-x86.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | x86
9 | win-x86
10 | true
11 | False
12 | False
13 | True
14 | True
15 |
16 |
--------------------------------------------------------------------------------
/src/Common/WindowsAdvancedSettings.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Common
5 | x86;x64;arm64
6 | win-x86;win-x64;win-arm64
7 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
8 | enable
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/FileExplorerGitIntegration.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Exe
7 |
8 |
9 | WinExe
10 |
11 |
12 |
13 | enable
14 | enable
15 | Dev
16 | portable
17 | x86;x64;arm64
18 | win-x86;win-x64;win-arm64
19 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
20 | true
21 |
22 |
23 |
24 |
25 | Guard
26 | Spectre
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Always
56 |
57 |
58 |
59 |
60 | $(DefineConstants);STABLE_BUILD
61 |
62 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/GitLocalRepositoryProviderFactory`1.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Runtime.InteropServices;
6 | using FileExplorerGitIntegration.Models;
7 | using WinRT;
8 |
9 | namespace FileExplorerGitIntegration;
10 |
11 | [ComVisible(true)]
12 | public class GitLocalRepositoryProviderFactory : IClassFactory
13 | where T : GitLocalRepositoryProviderFactory
14 | {
15 | private readonly Func _createGitLocalRepositoryProvider;
16 |
17 | public GitLocalRepositoryProviderFactory(Func createGitLocalRepositoryProvider)
18 | {
19 | _createGitLocalRepositoryProvider = createGitLocalRepositoryProvider;
20 | }
21 |
22 | public int CreateInstance(nint pUnkOuter, ref Guid riid, out nint ppvObject)
23 | {
24 | ppvObject = nint.Zero;
25 |
26 | if (pUnkOuter != nint.Zero)
27 | {
28 | Marshal.ThrowExceptionForHR(CLASSENOAGGREGATION);
29 | }
30 |
31 | if (riid == typeof(T).GUID || riid == Guid.Parse(Guids.IUnknown))
32 | {
33 | // Create the instance of the .NET object
34 | ppvObject = MarshalInspectable.FromManaged(_createGitLocalRepositoryProvider());
35 | }
36 | else
37 | {
38 | // The object that ppvObject points to does not support the
39 | // interface identified by riid.
40 | Marshal.ThrowExceptionForHR(ENOINTERFACE);
41 | }
42 |
43 | return 0;
44 | }
45 |
46 | int IClassFactory.LockServer(bool fLock)
47 | {
48 | return 0;
49 | }
50 |
51 | private const int CLASSENOAGGREGATION = unchecked((int)0x80040110);
52 | private const int ENOINTERFACE = unchecked((int)0x80004002);
53 | }
54 |
55 | internal static class Guids
56 | {
57 | public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
58 | public const string IUnknown = "00000000-0000-0000-C000-000000000046";
59 | }
60 |
61 | // IClassFactory declaration
62 | [ComImport]
63 | [ComVisible(false)]
64 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
65 | [Guid(Guids.IClassFactory)]
66 | internal interface IClassFactory
67 | {
68 | [PreserveSig]
69 | int CreateInstance(nint pUnkOuter, ref Guid riid, out nint ppvObject);
70 |
71 | [PreserveSig]
72 | int LockServer(bool fLock);
73 | }
74 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Helpers/Resources.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.Windows.ApplicationModel.Resources;
5 | using Serilog;
6 | using WindowsAdvancedSettings.Common.Helpers;
7 |
8 | namespace FileExplorerGitIntegration.Helpers;
9 |
10 | public static class Resources
11 | {
12 | private static ResourceLoader? _resourceLoader;
13 |
14 | public static string GetResource(string identifier, ILogger? log = null)
15 | {
16 | try
17 | {
18 | if (_resourceLoader == null)
19 | {
20 | if (RuntimeHelper.IsPackaged)
21 | {
22 | // Packaged resource map will be in the merged PRI.
23 | _resourceLoader = new ResourceLoader(ResourceLoader.GetDefaultResourceFilePath(), "Resources");
24 | }
25 | else
26 | {
27 | // Unpackaged will not be merged and will instead be in the named PRI.
28 | _resourceLoader = new ResourceLoader("FileExplorerGitIntegration.pri", "FileExplorerGitIntegration/Resources");
29 | }
30 | }
31 |
32 | return _resourceLoader.GetString(identifier);
33 | }
34 | catch (Exception ex)
35 | {
36 | log?.Error(ex, $"Failed loading resource: {identifier}");
37 |
38 | // If we fail, load the original identifier so it is obvious which resource is missing.
39 | return identifier;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/CommitLogCache.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Serilog;
5 |
6 | namespace FileExplorerGitIntegration.Models;
7 |
8 | internal sealed class CommitLogCache
9 | {
10 | private readonly string _workingDirectory;
11 |
12 | // For now, we'll use the command line to get the last commit for a file, on demand.
13 | private readonly GitDetect _gitDetect = new();
14 | private readonly bool _gitInstalled;
15 |
16 | private readonly LruCacheDictionary _cache = new();
17 |
18 | private readonly Serilog.ILogger _log = Log.ForContext("SourceContext", nameof(CommitLogCache));
19 |
20 | public CommitLogCache(string workingDirectory)
21 | {
22 | _workingDirectory = workingDirectory;
23 | _gitInstalled = _gitDetect.DetectGit();
24 | }
25 |
26 | public CommitWrapper? FindLastCommit(string relativePath)
27 | {
28 | if (_cache.TryGetValue(relativePath, out var cachedCommit))
29 | {
30 | return cachedCommit;
31 | }
32 |
33 | var result = FindLastCommitUsingCommandLine(relativePath);
34 |
35 | if (result != null)
36 | {
37 | result = _cache.GetOrAdd(relativePath, result);
38 | }
39 |
40 | return result;
41 | }
42 |
43 | private CommitWrapper? FindLastCommitUsingCommandLine(string relativePath)
44 | {
45 | if (string.IsNullOrEmpty(relativePath))
46 | {
47 | relativePath = ".";
48 | }
49 |
50 | var fullPath = Path.Combine(_workingDirectory, relativePath);
51 | var directory = Path.GetDirectoryName(fullPath);
52 | var filename = Path.GetFileName(fullPath);
53 | if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(filename))
54 | {
55 | _log.Warning($"FindLastCommitUsingCommandLine failed to parse relativePath {relativePath}");
56 | return null;
57 | }
58 |
59 | var result = GitExecute.ExecuteGitCommand(_gitDetect.GitConfiguration.ReadInstallPath(), directory, $"log -n 1 --pretty=format:%s%n%an%n%ae%n%aI%n%H -- {filename}");
60 | if ((result.Status != Microsoft.Windows.DevHome.SDK.ProviderOperationStatus.Success) || (result.Output is null))
61 | {
62 | return null;
63 | }
64 |
65 | var parts = result.Output.Split('\n');
66 | if (parts.Length != 5)
67 | {
68 | return null;
69 | }
70 |
71 | string message = parts[0];
72 | string authorName = parts[1];
73 | string authorEmail = parts[2];
74 | DateTimeOffset authorWhen = DateTimeOffset.Parse(parts[3], null, System.Globalization.DateTimeStyles.RoundtripKind);
75 | string sha = parts[4];
76 | return new CommitWrapper(message, authorName, authorEmail, authorWhen, sha);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/CommitWrapper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace FileExplorerGitIntegration.Models;
5 |
6 | public sealed class CommitWrapper
7 | {
8 | public string MessageShort { get; private set; }
9 |
10 | public string AuthorName { get; private set; }
11 |
12 | public string AuthorEmail { get; private set; }
13 |
14 | public DateTimeOffset AuthorWhen { get; private set; }
15 |
16 | public string Sha { get; private set; }
17 |
18 | public CommitWrapper(string messageShort, string authorName, string authorEmail, DateTimeOffset authorWhen, string sha)
19 | {
20 | MessageShort = messageShort;
21 | AuthorName = authorName;
22 | AuthorEmail = authorEmail;
23 | AuthorWhen = authorWhen;
24 | Sha = sha;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitConfiguration.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Serilog;
5 | using Windows.Storage;
6 | using WindowsAdvancedSettings.Common.Helpers;
7 |
8 | namespace FileExplorerGitIntegration.Models;
9 |
10 | public class GitConfiguration : IDisposable
11 | {
12 | public GitExecutableConfigOptions GitExecutableConfigOptions { get; set; }
13 |
14 | private readonly FileService _fileService;
15 |
16 | private string GitExeInstallPath { get; set; } = string.Empty;
17 |
18 | private static readonly object _fileLock = new();
19 |
20 | private readonly ILogger _log = Log.ForContext();
21 |
22 | private readonly string _tempConfigurationFileName = "TemporaryGitConfiguration.json";
23 |
24 | public GitConfiguration(string? path)
25 | {
26 | string folderPath;
27 | if (RuntimeHelper.IsPackaged)
28 | {
29 | folderPath = ApplicationData.Current.LocalFolder.Path;
30 | }
31 | else
32 | {
33 | folderPath = path ?? string.Empty;
34 | }
35 |
36 | GitExecutableConfigOptions = new GitExecutableConfigOptions
37 | {
38 | GitExecutableConfigFolderPath = folderPath,
39 | };
40 |
41 | _fileService = new FileService();
42 | EnsureConfigFileCreation();
43 | }
44 |
45 | public string ReadInstallPath()
46 | {
47 | lock (_fileLock)
48 | {
49 | GitExeInstallPath = _fileService.Read(GitExecutableConfigOptions.GitExecutableConfigFolderPath, GitExecutableConfigOptions.GitExecutableConfigFileName);
50 | return GitExeInstallPath;
51 | }
52 | }
53 |
54 | public void EnsureConfigFileCreation()
55 | {
56 | lock (_fileLock)
57 | {
58 | if (!Directory.Exists(GitExecutableConfigOptions.GitExecutableConfigFolderPath))
59 | {
60 | Directory.CreateDirectory(GitExecutableConfigOptions.GitExecutableConfigFolderPath);
61 | }
62 |
63 | var configFileFullPath = Path.Combine(GitExecutableConfigOptions.GitExecutableConfigFolderPath, GitExecutableConfigOptions.GitExecutableConfigFileName);
64 | if (!File.Exists(configFileFullPath))
65 | {
66 | _fileService.Save(GitExecutableConfigOptions.GitExecutableConfigFolderPath, GitExecutableConfigOptions.GitExecutableConfigFileName, string.Empty);
67 | _log.Information("The git configuration file did not exists and has just been created");
68 | }
69 | }
70 | }
71 |
72 | public bool IsGitExeInstallPathSet()
73 | {
74 | return !string.IsNullOrEmpty(GitExeInstallPath);
75 | }
76 |
77 | public bool StoreGitExeInstallPath(string path)
78 | {
79 | lock (_fileLock)
80 | {
81 | _log.Information("Setting Git Exe Install Path");
82 | GitExeInstallPath = path;
83 |
84 | _fileService.Save(GitExecutableConfigOptions.GitExecutableConfigFolderPath, _tempConfigurationFileName, GitExeInstallPath);
85 | File.Replace(Path.Combine(GitExecutableConfigOptions.GitExecutableConfigFolderPath, _tempConfigurationFileName), Path.Combine(GitExecutableConfigOptions.GitExecutableConfigFolderPath, GitExecutableConfigOptions.GitExecutableConfigFileName), null);
86 | _log.Information("Git Exe Install Path stored successfully");
87 | return true;
88 | }
89 | }
90 |
91 | public void Dispose()
92 | {
93 | GC.SuppressFinalize(this);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitDetect.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.Win32;
5 | using Microsoft.Windows.DevHome.SDK;
6 | using Serilog;
7 | using WindowsAdvancedSettings.Models;
8 |
9 | namespace FileExplorerGitIntegration.Models;
10 |
11 | public class GitDetect
12 | {
13 | public GitConfiguration GitConfiguration { get; set; }
14 |
15 | private readonly ILogger _log = Log.ForContext();
16 |
17 | private struct DetectInfo
18 | {
19 | public bool Found;
20 | public string Version;
21 | }
22 |
23 | public GitDetect()
24 | {
25 | GitConfiguration = new GitConfiguration(null);
26 | }
27 |
28 | public bool DetectGit()
29 | {
30 | var detect = new DetectInfo { Found = false, Version = string.Empty };
31 | var status = GitDetectStatus.NotFound;
32 |
33 | if (!detect.Found)
34 | {
35 | // Check if git.exe is present in PATH environment variable
36 | detect = ValidateGitConfigurationPath("git.exe");
37 | if (detect.Found)
38 | {
39 | status = GitDetectStatus.PathEnvironmentVariable;
40 | GitConfiguration.StoreGitExeInstallPath("git.exe");
41 | }
42 | }
43 |
44 | if (!detect.Found)
45 | {
46 | // Check execution of git.exe by finding install location in registry keys
47 | string[] registryPaths = { "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" };
48 |
49 | foreach (var registryPath in registryPaths)
50 | {
51 | var gitPath = Registry.GetValue(registryPath, "InstallLocation", defaultValue: string.Empty) as string;
52 | if (!string.IsNullOrEmpty(gitPath))
53 | {
54 | var paths = FindSubdirectories(gitPath);
55 | detect = CheckForExeInPaths(paths);
56 | if (detect.Found)
57 | {
58 | status = GitDetectStatus.RegistryProbe;
59 | break;
60 | }
61 | }
62 | }
63 | }
64 |
65 | if (!detect.Found)
66 | {
67 | // Search for git.exe in common file paths
68 | var programFiles = Environment.GetEnvironmentVariable("ProgramFiles");
69 | var programFilesX86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
70 | string[] possiblePaths = { $"{programFiles}\\Git\\bin", $"{programFilesX86}\\Git\\bin", $"{programFiles}\\Git\\cmd", $"{programFilesX86}\\Git\\cmd" };
71 | detect = CheckForExeInPaths(possiblePaths);
72 | if (detect.Found)
73 | {
74 | status = GitDetectStatus.ProgramFiles;
75 | }
76 | }
77 |
78 | _log.Information($"Status: {status}");
79 | ////TelemetryFactory.Get().Log("GitDetect_Event", LogLevel.Critical, new GitDetectEvent(status, detect.Version));
80 | return detect.Found;
81 | }
82 |
83 | private string[] FindSubdirectories(string installLocation)
84 | {
85 | try
86 | {
87 | if (Directory.Exists(installLocation))
88 | {
89 | return Directory.GetDirectories(installLocation);
90 | }
91 | else
92 | {
93 | _log.Warning("Install location does not exist: {InstallLocation}", installLocation);
94 | return Array.Empty();
95 | }
96 | }
97 | catch (Exception ex)
98 | {
99 | _log.Warning(ex, "Failed to find subdirectories in install location: {InstallLocation}", installLocation);
100 | return Array.Empty();
101 | }
102 | }
103 |
104 | private DetectInfo CheckForExeInPaths(string[] possiblePaths)
105 | {
106 | // Iterate through the possible paths to find the git.exe file
107 | foreach (var path in possiblePaths.Where(x => !string.IsNullOrEmpty(x)))
108 | {
109 | var gitPath = Path.Combine(path, "git.exe");
110 | var detect = ValidateGitConfigurationPath(gitPath);
111 |
112 | // If the git.exe file is found, store the install path and log the information
113 | if (detect.Found)
114 | {
115 | GitConfiguration.StoreGitExeInstallPath(gitPath);
116 | _log.Information("Git Exe Install Path found");
117 | return detect;
118 | }
119 | }
120 |
121 | _log.Debug("Git.exe not found in paths examined");
122 | return new DetectInfo { Found = false, Version = string.Empty };
123 | }
124 |
125 | private DetectInfo ValidateGitConfigurationPath(string path)
126 | {
127 | var result = GitExecute.ExecuteGitCommand(path, string.Empty, "--version");
128 | if (result.Status == ProviderOperationStatus.Success && result.Output != null && result.Output.Contains("git version"))
129 | {
130 | return new DetectInfo { Found = true, Version = result.Output.Replace("git version", string.Empty).TrimEnd() };
131 | }
132 |
133 | return new DetectInfo { Found = false, Version = string.Empty };
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitDetectStatus.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace WindowsAdvancedSettings.Models;
5 |
6 | public enum GitDetectStatus
7 | {
8 | // git.exe was not found on the system
9 | NotFound,
10 |
11 | // In the PATH environment variable
12 | PathEnvironmentVariable,
13 |
14 | // Probed well-known registry keys to find a Git install location
15 | RegistryProbe,
16 |
17 | // Probed well-known folders under Program Files [(x86)]
18 | ProgramFiles,
19 | }
20 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitExeceutableConfigOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace FileExplorerGitIntegration.Models;
5 |
6 | public partial class GitExecutableConfigOptions
7 | {
8 | private const string GitExecutableConfigFileNameDefault = "GitConfiguration.json";
9 |
10 | public string GitExecutableConfigFileName { get; set; } = GitExecutableConfigFileNameDefault;
11 |
12 | private readonly string _gitExecutableConfigFolderPathDefault = Path.Combine(Path.GetTempPath(), "FileExplorerGitIntegration");
13 |
14 | private string? _gitExecutableConfigFolderPath;
15 |
16 | public string GitExecutableConfigFolderPath
17 | {
18 | get => _gitExecutableConfigFolderPath is null ? _gitExecutableConfigFolderPathDefault : _gitExecutableConfigFolderPath;
19 | set => _gitExecutableConfigFolderPath = string.IsNullOrEmpty(value) ? _gitExecutableConfigFolderPathDefault : value;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitExecute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Diagnostics;
6 | using Microsoft.Windows.DevHome.SDK;
7 | using Serilog;
8 | using WindowsAdvancedSettings.Common.Helpers;
9 |
10 | namespace FileExplorerGitIntegration.Models;
11 |
12 | public class GitExecute
13 | {
14 | public static GitCommandRunnerResultInfo ExecuteGitCommand(string gitApplication, string repositoryDirectory, string arguments)
15 | {
16 | try
17 | {
18 | var processStartInfo = new ProcessStartInfo();
19 | if (!WslIntegrator.IsWSLRepo(repositoryDirectory))
20 | {
21 | processStartInfo.FileName = gitApplication;
22 | processStartInfo.Arguments = arguments;
23 | processStartInfo.WorkingDirectory = repositoryDirectory;
24 | }
25 | else
26 | {
27 | Log.Information("Wsl.exe will be invoked to obtain property information from git");
28 | processStartInfo.FileName = "wsl";
29 | processStartInfo.Arguments = string.Concat(WslIntegrator.GetArgumentPrefixForWsl(repositoryDirectory), arguments);
30 | processStartInfo.WorkingDirectory = WslIntegrator.GetWorkingDirectory(repositoryDirectory);
31 | }
32 |
33 | processStartInfo.RedirectStandardOutput = true;
34 | processStartInfo.UseShellExecute = false;
35 | processStartInfo.CreateNoWindow = true;
36 | processStartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
37 |
38 | using var process = Process.Start(processStartInfo);
39 | if (process != null)
40 | {
41 | var output = process.StandardOutput.ReadToEnd();
42 |
43 | // Add timeout for 1 minute
44 | process.WaitForExit(TimeSpan.FromMinutes(1));
45 |
46 | if (process.ExitCode != 0)
47 | {
48 | Log.Error("Execute Git process exited unsuccessfully with exit code {ExitCode}", process.ExitCode);
49 | return new GitCommandRunnerResultInfo(ProviderOperationStatus.Failure, output, "Execute Git process exited unsuccessfully", string.Empty, null, arguments, process.ExitCode);
50 | }
51 |
52 | return new GitCommandRunnerResultInfo(ProviderOperationStatus.Success, output);
53 | }
54 | else
55 | {
56 | Log.Error("Failed to start the Git process: process is null");
57 | return new GitCommandRunnerResultInfo(ProviderOperationStatus.Failure, null, "Git process is null", string.Empty, new InvalidOperationException("Failed to start the Git process: process is null"), null, null);
58 | }
59 | }
60 | catch (Exception ex)
61 | {
62 | Log.Error(ex, "Failed to invoke Git with arguments: {Argument}", arguments);
63 | return new GitCommandRunnerResultInfo(ProviderOperationStatus.Failure, null, "Failed to invoke Git with arguments", string.Empty, ex, arguments, null);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitLocalRepository.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Runtime.InteropServices;
5 | using Microsoft.Windows.DevHome.SDK;
6 | using Serilog;
7 | using Windows.Foundation.Collections;
8 |
9 | namespace FileExplorerGitIntegration.Models;
10 |
11 | [ComVisible(false)]
12 | [ClassInterface(ClassInterfaceType.None)]
13 | public sealed class GitLocalRepository : ILocalRepository
14 | {
15 | private readonly RepositoryCache? _repositoryCache;
16 |
17 | private readonly ILogger _log = Log.ForContext("SourceContext", nameof(GitLocalRepository));
18 |
19 | public string RootFolder
20 | {
21 | get; init;
22 | }
23 |
24 | public GitLocalRepository(string rootFolder)
25 | {
26 | RootFolder = rootFolder;
27 | }
28 |
29 | internal GitLocalRepository(string rootFolder, RepositoryCache? cache)
30 | {
31 | RootFolder = rootFolder;
32 | _repositoryCache = cache;
33 |
34 | try
35 | {
36 | // Rather than open the repo from scratch as validation, try to retrieve it from the cache
37 | OpenRepository();
38 | }
39 | catch (Exception ex)
40 | {
41 | _log.Error(ex, "Exception thrown in OpenRepository");
42 | throw;
43 | }
44 | }
45 |
46 | private RepositoryWrapper OpenRepository()
47 | {
48 | if (_repositoryCache != null)
49 | {
50 | return _repositoryCache.GetRepository(RootFolder);
51 | }
52 |
53 | return new RepositoryWrapper(RootFolder);
54 | }
55 |
56 | IPropertySet ILocalRepository.GetProperties(string[] properties, string relativePath)
57 | {
58 | relativePath = relativePath.Replace('\\', '/');
59 | var result = new ValueSet();
60 |
61 | (CommitWrapper? commit, bool alreadyFetched) latestCommit = (null, false);
62 |
63 | var repository = OpenRepository();
64 |
65 | if (repository is null)
66 | {
67 | _log.Debug("GetProperties: Repository object is null");
68 | return result;
69 | }
70 |
71 | // If this repo wasn't fetched from the cache, we'll need to dispose of it at the end of the method.
72 | using var repositoryCleanup = (_repositoryCache is null) ? repository : null;
73 |
74 | foreach (var propName in properties)
75 | {
76 | switch (propName)
77 | {
78 | case "System.VersionControl.LastChangeMessage":
79 | case "System.VersionControl.LastChangeAuthorName":
80 | case "System.VersionControl.LastChangeDate":
81 | case "System.VersionControl.LastChangeAuthorEmail":
82 | case "System.VersionControl.LastChangeID":
83 | AddLatestCommitProperty(result, relativePath, propName, repository, ref latestCommit);
84 | break;
85 |
86 | case "System.VersionControl.Status":
87 | result.Add(propName, GetStatus(relativePath, repository));
88 | break;
89 |
90 | case "System.VersionControl.CurrentFolderStatus":
91 | var folderStatus = GetFolderStatus(relativePath, repository);
92 | if (folderStatus is not null)
93 | {
94 | result.Add(propName, folderStatus);
95 | }
96 |
97 | break;
98 | }
99 | }
100 |
101 | _log.Debug("Returning source control properties from git source control extension");
102 | return result;
103 | }
104 |
105 | private void AddLatestCommitProperty(ValueSet result, string relativePath, string propName, RepositoryWrapper repository, ref (CommitWrapper? commit, bool alreadyFetched) latestCommit)
106 | {
107 | if (!latestCommit.alreadyFetched)
108 | {
109 | latestCommit.commit = FindLatestCommit(relativePath, repository);
110 | latestCommit.alreadyFetched = true;
111 | }
112 |
113 | if (latestCommit.commit is not null)
114 | {
115 | switch (propName)
116 | {
117 | case "System.VersionControl.LastChangeMessage":
118 | result.Add(propName, latestCommit.commit.MessageShort);
119 | break;
120 | case "System.VersionControl.LastChangeAuthorName":
121 | result.Add(propName, latestCommit.commit.AuthorName);
122 | break;
123 | case "System.VersionControl.LastChangeDate":
124 | result.Add(propName, latestCommit.commit.AuthorWhen);
125 | break;
126 | case "System.VersionControl.LastChangeAuthorEmail":
127 | result.Add(propName, latestCommit.commit.AuthorEmail);
128 | break;
129 | case "System.VersionControl.LastChangeID":
130 | result.Add(propName, latestCommit.commit.Sha);
131 | break;
132 | }
133 | }
134 | }
135 |
136 | public IPropertySet GetProperties(string[] properties, string relativePath)
137 | {
138 | return ((ILocalRepository)this).GetProperties(properties, relativePath);
139 | }
140 |
141 | private string? GetFolderStatus(string relativePath, RepositoryWrapper repository)
142 | {
143 | try
144 | {
145 | return repository.GetRepoStatus(relativePath);
146 | }
147 | catch
148 | {
149 | return null;
150 | }
151 | }
152 |
153 | private string? GetStatus(string relativePath, RepositoryWrapper repository)
154 | {
155 | try
156 | {
157 | return repository.GetFileStatus(relativePath);
158 | }
159 | catch
160 | {
161 | return null;
162 | }
163 | }
164 |
165 | private CommitWrapper? FindLatestCommit(string relativePath, RepositoryWrapper repository)
166 | {
167 | try
168 | {
169 | return repository.FindLastCommit(relativePath);
170 | }
171 | catch
172 | {
173 | return null;
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitLocalRepositoryProviderFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Runtime.InteropServices;
5 | using FileExplorerGitIntegration.Helpers;
6 | using Microsoft.Windows.DevHome.SDK;
7 | using Serilog;
8 |
9 | namespace FileExplorerGitIntegration.Models;
10 |
11 | [ComVisible(true)]
12 | [ClassInterface(ClassInterfaceType.None)]
13 | #if !DEBUG
14 | [Guid("8A962CBD-530D-4195-8FE3-F0DF3FDDF128")]
15 | #else
16 | [Guid("BDA76685-E749-4f09-8F13-C466D0802DA1")]
17 | #endif
18 | public class GitLocalRepositoryProviderFactory : ILocalRepositoryProvider
19 | {
20 | private readonly RepositoryCache? _repositoryCache;
21 |
22 | public string DisplayName => "GitLocalRepositoryProviderFactory";
23 |
24 | private readonly string _errorResourceKey = "OpenRepositoryError";
25 |
26 | private readonly ILogger _log = Log.ForContext("SourceContext", nameof(GitLocalRepositoryProviderFactory));
27 |
28 | GetLocalRepositoryResult ILocalRepositoryProvider.GetRepository(string rootPath)
29 | {
30 | try
31 | {
32 | return new GetLocalRepositoryResult(new GitLocalRepository(rootPath, _repositoryCache));
33 | }
34 | catch (ArgumentException ex)
35 | {
36 | _log.Error(ex, "GitLocalRepositoryProviderFactory: Failed to create GitLocalRepository");
37 | return new GetLocalRepositoryResult(ex, Resources.GetResource("RepositoryNotFound"), $"Message: {ex.Message} and HRESULT: {ex.HResult}");
38 | }
39 | catch (Exception ex)
40 | {
41 | _log.Error(ex, "GitLocalRepositoryProviderFactory: Failed to create GitLocalRepository");
42 | if (ex.Message.Contains("not owned by current user") || ex.Message.Contains("detected dubious ownership in repository"))
43 | {
44 | return new GetLocalRepositoryResult(ex, Resources.GetResource("RepositoryNotOwnedByCurrentUser"), $"Message: {ex.Message} and HRESULT: {ex.HResult}");
45 | }
46 |
47 | return new GetLocalRepositoryResult(ex, Resources.GetResource(_errorResourceKey), $"Message: {ex.Message} and HRESULT: {ex.HResult}");
48 | }
49 | }
50 |
51 | public GetLocalRepositoryResult GetRepository(string rootPath)
52 | {
53 | return ((ILocalRepositoryProvider)this).GetRepository(rootPath);
54 | }
55 |
56 | public GitLocalRepositoryProviderFactory(RepositoryCache cache)
57 | {
58 | _repositoryCache = cache;
59 | }
60 |
61 | public GitLocalRepositoryProviderFactory()
62 | {
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitLocalRepositoryProviderServer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Diagnostics;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Runtime.InteropServices;
7 | using FileExplorerGitIntegration;
8 | using Serilog;
9 | using Windows.Win32;
10 | using Windows.Win32.System.Com;
11 |
12 | namespace FileExplorerGitIntegration.Models;
13 |
14 | public sealed class GitLocalRepositoryProviderServer : IDisposable
15 | {
16 | private readonly HashSet _registrationCookies = new();
17 | private readonly Serilog.ILogger _log = Log.ForContext("SourceContext", nameof(GitLocalRepositoryProviderServer));
18 |
19 | [UnconditionalSuppressMessage(
20 | "ReflectionAnalysis",
21 | "IL2050:COMCorrectness",
22 | Justification = "GitLocalRepositoryProviderFactory and all the interfaces it implements are defined in an assembly that is not marked trimmable which means the relevant interfaces won't be trimmed.")]
23 | public void RegisterGitRepositoryProviderServer(Func createGitRepositoryProviderServer)
24 | where T : GitLocalRepositoryProviderFactory
25 | {
26 | _log.Debug($"Registering class object:");
27 | _log.Debug($"CLSID: {typeof(T).GUID:B}");
28 | _log.Debug($"Type: {typeof(T)}");
29 |
30 | uint cookie;
31 | var clsid = typeof(T).GUID;
32 | var hr = PInvoke.CoRegisterClassObject(
33 | clsid,
34 | new GitLocalRepositoryProviderFactory(createGitRepositoryProviderServer),
35 | CLSCTX.CLSCTX_LOCAL_SERVER,
36 | Ole32.REGCLS_MULTIPLEUSE | Ole32.REGCLS_SUSPENDED,
37 | out cookie);
38 |
39 | if (hr < 0)
40 | {
41 | Marshal.ThrowExceptionForHR(hr);
42 | }
43 |
44 | _registrationCookies.Add(cookie);
45 | _log.Debug($"Cookie: {cookie}");
46 | hr = PInvoke.CoResumeClassObjects();
47 | if (hr < 0)
48 | {
49 | Marshal.ThrowExceptionForHR(hr);
50 | }
51 | }
52 |
53 | public void Run()
54 | {
55 | // TODO : We need to handle lifetime management of the server.
56 | // For details around ref counting and locking of out-of-proc COM servers, see
57 | // https://docs.microsoft.com/windows/win32/com/out-of-process-server-implementation-helpers
58 | // https://github.com/microsoft/devhome/issues/645
59 | Console.ReadLine();
60 | var disposedEvent = new ManualResetEvent(false);
61 | disposedEvent.WaitOne();
62 | }
63 |
64 | public void Dispose()
65 | {
66 | _log.Debug($"Revoking class object registrations:");
67 | foreach (var cookie in _registrationCookies)
68 | {
69 | _log.Debug($"Cookie: {cookie}");
70 | var hr = PInvoke.CoRevokeClassObject(cookie);
71 | Debug.Assert(hr >= 0, $"CoRevokeClassObject failed ({hr:x}). Cookie: {cookie}");
72 | }
73 | }
74 |
75 | private sealed class Ole32
76 | {
77 | #pragma warning disable SA1310 // Field names should not contain underscore
78 | // https://docs.microsoft.com/windows/win32/api/combaseapi/ne-combaseapi-regcls
79 | public const REGCLS REGCLS_MULTIPLEUSE = (REGCLS)1;
80 | public const REGCLS REGCLS_SUSPENDED = (REGCLS)4;
81 | #pragma warning restore SA1310 // Field names should not contain underscore
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitRepositoryStatus.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using LibGit2Sharp;
5 |
6 | namespace FileExplorerGitIntegration.Models;
7 |
8 | internal sealed class GitRepositoryStatus
9 | {
10 | private readonly Dictionary _fileEntries = new();
11 | private readonly Dictionary _submoduleEntries = new();
12 | private readonly Dictionary> _statusEntries = new();
13 |
14 | public string BranchName { get; set; } = string.Empty;
15 |
16 | public bool IsHeadDetached { get; set; }
17 |
18 | public string UpstreamBranch { get; set; } = string.Empty;
19 |
20 | public int AheadBy { get; set; }
21 |
22 | public int BehindBy { get; set; }
23 |
24 | public string Sha { get; set; } = string.Empty;
25 |
26 | public GitRepositoryStatus()
27 | {
28 | _statusEntries.Add(FileStatus.NewInIndex, new List());
29 | _statusEntries.Add(FileStatus.ModifiedInIndex, new List());
30 | _statusEntries.Add(FileStatus.DeletedFromIndex, new List());
31 | _statusEntries.Add(FileStatus.NewInWorkdir, new List());
32 | _statusEntries.Add(FileStatus.ModifiedInWorkdir, new List());
33 | _statusEntries.Add(FileStatus.DeletedFromWorkdir, new List());
34 | _statusEntries.Add(FileStatus.RenamedInIndex, new List());
35 | _statusEntries.Add(FileStatus.RenamedInWorkdir, new List());
36 | _statusEntries.Add(FileStatus.Conflicted, new List());
37 | }
38 |
39 | public void Add(string path, GitStatusEntry status)
40 | {
41 | _fileEntries.Add(path, status);
42 | foreach (var entry in _statusEntries)
43 | {
44 | if (status.Status.HasFlag(entry.Key))
45 | {
46 | entry.Value.Add(status);
47 | }
48 | }
49 | }
50 |
51 | public bool TryAdd(string path, SubmoduleStatus status)
52 | {
53 | return _submoduleEntries.TryAdd(path, status);
54 | }
55 |
56 | public Dictionary FileEntries => _fileEntries;
57 |
58 | public List Added => _statusEntries[FileStatus.NewInIndex];
59 |
60 | public List Staged => _statusEntries[FileStatus.ModifiedInIndex];
61 |
62 | public List Removed => _statusEntries[FileStatus.DeletedFromIndex];
63 |
64 | public List Untracked => _statusEntries[FileStatus.NewInWorkdir];
65 |
66 | public List Modified => _statusEntries[FileStatus.ModifiedInWorkdir];
67 |
68 | public List Missing => _statusEntries[FileStatus.DeletedFromWorkdir];
69 |
70 | public List RenamedInIndex => _statusEntries[FileStatus.RenamedInIndex];
71 |
72 | public List RenamedInWorkDir => _statusEntries[FileStatus.RenamedInWorkdir];
73 |
74 | public List Conflicted => _statusEntries[FileStatus.Conflicted];
75 |
76 | public Dictionary SubmoduleEntries => _submoduleEntries;
77 | }
78 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/GitStatusEntry.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | using LibGit2Sharp;
7 |
8 | namespace FileExplorerGitIntegration.Models;
9 |
10 | [DebuggerDisplay("{DebuggerDisplay,nq}")]
11 | internal sealed class GitStatusEntry
12 | {
13 | public GitStatusEntry(string path, FileStatus status, string? renameOldPath = null)
14 | {
15 | Path = path;
16 | Status = status;
17 | RenameOldPath = renameOldPath;
18 | }
19 |
20 | public string Path { get; set; }
21 |
22 | public FileStatus Status { get; set; }
23 |
24 | public string? RenameOldPath { get; set; }
25 |
26 | public string? RenameNewPath { get; set; }
27 |
28 | private string DebuggerDisplay
29 | {
30 | get
31 | {
32 | if (Status.HasFlag(FileStatus.RenamedInIndex) || Status.HasFlag(FileStatus.RenamedInWorkdir))
33 | {
34 | return string.Format(CultureInfo.InvariantCulture, "{0}: {1} -> {2}", Status, RenameOldPath, Path);
35 | }
36 |
37 | return string.Format(CultureInfo.InvariantCulture, "{0}: {1}", Status, Path);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/LruCacheDictionary.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Diagnostics;
5 |
6 | namespace FileExplorerGitIntegration.Models;
7 |
8 | // A simple LRU cache dictionary implementation.
9 | [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "File names for generics are ugly.")]
10 | internal sealed class LruCacheDictionary
11 | where TKey : notnull
12 | {
13 | private readonly int _capacity;
14 | private const int DefaultCapacity = 512;
15 | private readonly object _lock = new();
16 | private readonly Dictionary> _dict;
17 | private readonly LinkedList<(TKey, TValue)> _lruList = new();
18 |
19 | public LruCacheDictionary(int capacity = DefaultCapacity)
20 | {
21 | _capacity = capacity;
22 | _dict = [];
23 | }
24 |
25 | public bool TryGetValue(TKey key, out TValue value)
26 | {
27 | lock (_lock)
28 | {
29 | if (_dict.TryGetValue(key, out var node))
30 | {
31 | RenewExistingNodeNoLock(node);
32 | value = node.Value.Item2;
33 | return true;
34 | }
35 |
36 | value = default!;
37 | return false;
38 | }
39 | }
40 |
41 | public TValue GetOrAdd(TKey key, TValue value)
42 | {
43 | lock (_lock)
44 | {
45 | if (_dict.TryGetValue(key, out var node))
46 | {
47 | RenewExistingNodeNoLock(node);
48 | return node.Value.Item2;
49 | }
50 |
51 | AddAndTrimNoLock(key, value);
52 | return value;
53 | }
54 | }
55 |
56 | private void AddAndTrimNoLock(TKey key, TValue value)
57 | {
58 | var newNode = new LinkedListNode<(TKey, TValue)>((key, value));
59 | AddNewNodeNoLock(newNode);
60 | TrimToCapacityNoLock();
61 | }
62 |
63 | private void RenewExistingNodeNoLock(LinkedListNode<(TKey, TValue)> node)
64 | {
65 | Debug.Assert(node.List == _lruList, "Node is not in the list");
66 | _lruList.Remove(node);
67 | _lruList.AddLast(node);
68 | }
69 |
70 | private void AddNewNodeNoLock(LinkedListNode<(TKey, TValue)> node)
71 | {
72 | Debug.Assert(node.List == null, "Node is already in the list");
73 | _lruList.AddLast(node);
74 | _dict.Add(node.Value.Item1, node);
75 | }
76 |
77 | private void TrimToCapacityNoLock()
78 | {
79 | if (_lruList.Count > _capacity)
80 | {
81 | var node = _lruList.First;
82 | if (node != null)
83 | {
84 | _lruList.RemoveFirst();
85 | _dict.Remove(node.Value.Item1);
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/RepositoryCache.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Concurrent;
5 | using System.Diagnostics;
6 | using Serilog;
7 |
8 | namespace FileExplorerGitIntegration.Models;
9 |
10 | public sealed class RepositoryCache : IDisposable
11 | {
12 | private readonly ConcurrentDictionary _repositoryCache = new();
13 | private readonly ILogger _log = Log.ForContext("SourceContext", nameof(RepositoryCache));
14 | private bool _disposedValue;
15 |
16 | public RepositoryWrapper GetRepository(string rootFolder)
17 | {
18 | if (_repositoryCache.TryGetValue(rootFolder, out RepositoryWrapper? repo))
19 | {
20 | return repo;
21 | }
22 |
23 | var tempRepo = new RepositoryWrapper(rootFolder);
24 | try
25 | {
26 | if (!_repositoryCache.TryAdd(rootFolder, tempRepo))
27 | {
28 | // Another thread beat us here. Dispose of the repo we just created and get the one from the cache.
29 | tempRepo.Dispose();
30 | var result = _repositoryCache.TryGetValue(rootFolder, out repo);
31 | Debug.Assert(result, "Failed to get newly added repo from cache");
32 | Debug.Assert(repo != null, "Repo from cache should not be null");
33 | }
34 | else
35 | {
36 | repo = tempRepo;
37 | tempRepo = null;
38 | }
39 | }
40 | finally
41 | {
42 | if (tempRepo != null)
43 | {
44 | tempRepo.Dispose();
45 | }
46 | }
47 |
48 | return repo;
49 | }
50 |
51 | internal void Dispose(bool disposing)
52 | {
53 | if (!_disposedValue)
54 | {
55 | if (disposing)
56 | {
57 | foreach (var repo in _repositoryCache.Values)
58 | {
59 | repo.Dispose();
60 | }
61 | }
62 |
63 | _repositoryCache.Clear();
64 | _disposedValue = true;
65 | }
66 | }
67 |
68 | void IDisposable.Dispose()
69 | {
70 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
71 | Dispose(disposing: true);
72 | GC.SuppressFinalize(this);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/ThrottledTask.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace FileExplorerGitIntegration.Models;
5 |
6 | internal sealed class ThrottledTask
7 | {
8 | private readonly TimeSpan _interval;
9 | private readonly object _lock = new();
10 |
11 | private readonly Action _action;
12 |
13 | private Task? _currentTask;
14 | private bool _shouldQueue;
15 |
16 | // Run an action, but ensure that `interval` time has elapsed after the last action completed before running again.
17 | // If a task is already running when Run is called again, we "queue" that request to execute after enough time has passed.
18 | // Multiple requests during this period of time result in only a single action being run after the waiting period.
19 | // In other words, when there is a rapid flood of calls to Run(), this is coalesced into:
20 | // - The first call to Run invokes _action immediately.
21 | // - The subsequent calls within the window are all coalesced into a second, delayed invoke of _action
22 | // - If more calls arrive during this second invoke, they are coalesced into a third, delayed invoke.
23 | // - and so on...
24 | public ThrottledTask(Action action, TimeSpan interval)
25 | {
26 | _action = action;
27 | _interval = interval;
28 | }
29 |
30 | // The first time Run is called, wait until new requests stop getting queued, checking every _interval, then create a task to invoke _action.
31 | // If Run is not called again while the task is active (during the action or cooldown)
32 | // then the task exits normally and resets state back to initial.
33 | // Otherwise, if Run is called again while the task is active,
34 | // then set _shouldQueue to true.
35 | // Now, when the action and cooldown complete, we'll loopback and execute one more call and reset the queue flag.
36 | public void Run(CancellationToken cancellationToken = default)
37 | {
38 | lock (_lock)
39 | {
40 | if (_currentTask != null && !_currentTask.IsCompleted)
41 | {
42 | _shouldQueue = true;
43 | return;
44 | }
45 |
46 | _currentTask = Task.Run(
47 | async () =>
48 | {
49 | bool runAgain = true;
50 | while (runAgain)
51 | {
52 | bool waitAgain = false;
53 | do
54 | {
55 | await Task.Delay(_interval, cancellationToken);
56 | lock (_lock)
57 | {
58 | if (_shouldQueue)
59 | {
60 | _shouldQueue = false;
61 | waitAgain = true;
62 | }
63 | else
64 | {
65 | waitAgain = false;
66 | }
67 | }
68 | }
69 | while (!waitAgain);
70 |
71 | _action.Invoke();
72 | lock (_lock)
73 | {
74 | if (_shouldQueue)
75 | {
76 | _shouldQueue = false;
77 | }
78 | else
79 | {
80 | runAgain = false;
81 | _currentTask = null;
82 | }
83 | }
84 | }
85 | },
86 | cancellationToken);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Models/WslIntegrator.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Diagnostics;
5 | using Serilog;
6 |
7 | namespace FileExplorerGitIntegration.Models;
8 |
9 | public class WslIntegrator
10 | {
11 | private static readonly string[] _wslPathPrefixes = { @"\\wsl$\", @"\\wsl.localhost\" };
12 | private static readonly ILogger _log = Log.ForContext();
13 |
14 | public static bool IsWSLRepo(string repositoryPath)
15 | {
16 | if (string.IsNullOrEmpty(repositoryPath))
17 | {
18 | _log.Debug($"The repository path is empty");
19 | return false;
20 | }
21 |
22 | if (repositoryPath.Contains(Path.AltDirectorySeparatorChar))
23 | {
24 | _log.Debug($"The repository path is not in the expected format: {repositoryPath}");
25 | return false;
26 | }
27 |
28 | // Check if the repository path contains any of the WSL path prefixes
29 | foreach (string prefix in _wslPathPrefixes)
30 | {
31 | if (repositoryPath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
32 | {
33 | return true;
34 | }
35 | }
36 |
37 | _log.Debug(repositoryPath + " is not a WSL path");
38 | return false;
39 | }
40 |
41 | public static string GetWslDistributionName(string repositoryPath)
42 | {
43 | if (string.IsNullOrEmpty(repositoryPath))
44 | {
45 | _log.Debug("The repository path is empty");
46 | throw new ArgumentException("Repository path is empty");
47 | }
48 |
49 | Debug.Assert(IsWSLRepo(repositoryPath), "the repository path must be a valid wsl path");
50 | if (!IsWSLRepo(repositoryPath))
51 | {
52 | throw new ArgumentException($"Not a valid WSL path: {repositoryPath}");
53 | }
54 |
55 | // Parse the repository path to get the distribution name
56 | string[] pathParts = repositoryPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
57 | if (pathParts.Length > 1)
58 | {
59 | return pathParts[1];
60 | }
61 |
62 | _log.Debug($"Failed to get the distribution name from the repository path: {repositoryPath}");
63 | throw new ArgumentException("Failed to get the distribution name from the repository path");
64 | }
65 |
66 | public static string GetWorkingDirectory(string repositoryPath)
67 | {
68 | if (string.IsNullOrEmpty(repositoryPath))
69 | {
70 | throw new ArgumentException("Repository path is empty");
71 | }
72 |
73 | Debug.Assert(IsWSLRepo(repositoryPath), "the repository path must be a valid wsl path");
74 | if (!IsWSLRepo(repositoryPath))
75 | {
76 | throw new ArgumentException($"Not a valid WSL path: {repositoryPath}");
77 | }
78 |
79 | string[] pathParts = repositoryPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
80 |
81 | // Ensure the first part is replaced with "\\wsl$"
82 | if (pathParts.Length > 0)
83 | {
84 | pathParts[0] = Path.DirectorySeparatorChar + "\\wsl$";
85 | }
86 |
87 | var workingDirPath = string.Join(Path.DirectorySeparatorChar.ToString(), pathParts);
88 | return workingDirPath;
89 | }
90 |
91 | public static string GetNormalizedLinuxPath(string repositoryPath)
92 | {
93 | if (string.IsNullOrEmpty(repositoryPath))
94 | {
95 | _log.Debug("The repository path is empty");
96 | return string.Empty;
97 | }
98 |
99 | string[] pathParts = repositoryPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
100 | var workingDirPath = string.Join(Path.DirectorySeparatorChar.ToString(), pathParts.Skip(2));
101 | workingDirPath = workingDirPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
102 | workingDirPath = workingDirPath.Insert(0, Path.AltDirectorySeparatorChar.ToString());
103 | return workingDirPath;
104 | }
105 |
106 | public static string GetArgumentPrefixForWsl(string repositoryPath)
107 | {
108 | if (string.IsNullOrEmpty(repositoryPath))
109 | {
110 | throw new ArgumentException("Repository path is empty");
111 | }
112 |
113 | Debug.Assert(IsWSLRepo(repositoryPath), "the repository path must be a valid wsl path");
114 | if (!IsWSLRepo(repositoryPath))
115 | {
116 | throw new ArgumentException($"Not a valid WSL path: {repositoryPath}");
117 | }
118 |
119 | return $"-d {GetWslDistributionName(repositoryPath)} git ";
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/NativeMethods.txt:
--------------------------------------------------------------------------------
1 | CoRegisterClassObject
2 | CoRevokeClassObject
3 | CoResumeClassObjects
4 | MEMORYSTATUSEX
5 | GlobalMemoryStatusEx
6 | SHChangeNotify
7 | SHLoadIndirectString
8 | GetCurrentPackageFullName
9 | OpenProcessToken
10 | GetTokenInformation
11 | GetCurrentProcess
12 | TOKEN_ELEVATION_TYPE
13 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using FileExplorerGitIntegration.Models;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.Windows.AppLifecycle;
7 | using Serilog;
8 | using Windows.ApplicationModel.Activation;
9 | using WindowsAdvancedSettings.Common.Helpers;
10 |
11 | namespace FileExplorerGitIntegration;
12 |
13 | public sealed class Program
14 | {
15 | [MTAThread]
16 | public static async Task Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
17 | {
18 | // Set up Logging
19 | Environment.SetEnvironmentVariable("WINDOWSADVANCEDSETTINGS_LOG_ROOT", Path.Join(Logging.LogFolderRoot, "FileExplorerGitIntegration"));
20 | var configuration = new ConfigurationBuilder()
21 | .AddJsonFile("appsettings_FileExplorerGitIntegration.json")
22 | .Build();
23 | Log.Logger = new LoggerConfiguration()
24 | .ReadFrom.Configuration(configuration)
25 | .CreateLogger();
26 |
27 | Log.Information($"Launched with args: {string.Join(' ', args.ToArray())}");
28 |
29 | // Force the app to be single instanced
30 | // Get or register the main instance
31 | var mainInstance = AppInstance.FindOrRegisterForKey("mainInstance");
32 | var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
33 |
34 | // If the main instance isn't this current instance
35 | if (!mainInstance.IsCurrent)
36 | {
37 | Log.Information($"Not main instance, redirecting.");
38 | await mainInstance.RedirectActivationToAsync(activationArgs);
39 | Log.CloseAndFlush();
40 | return;
41 | }
42 |
43 | // Otherwise, we're in the main instance
44 | // Register for activation redirection
45 | AppInstance.GetCurrent().Activated += AppActivationRedirected;
46 |
47 | if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
48 | {
49 | HandleCOMServerActivation();
50 | }
51 | else
52 | {
53 | Log.Warning("Not being launched as a ComServer... exiting.");
54 | }
55 |
56 | Log.CloseAndFlush();
57 | }
58 |
59 | private static void AppActivationRedirected(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments activationArgs)
60 | {
61 | Log.Information($"Redirected with kind: {activationArgs.Kind}");
62 |
63 | // Handle COM server
64 | if (activationArgs.Kind == ExtendedActivationKind.Launch)
65 | {
66 | var d = activationArgs.Data as ILaunchActivatedEventArgs;
67 | var args = d?.Arguments.Split();
68 |
69 | if (args?.Length > 1 && args[1] == "-RegisterProcessAsComServer")
70 | {
71 | Log.Information($"Activation COM Registration Redirect: {string.Join(' ', args.ToList())}");
72 | HandleCOMServerActivation();
73 | }
74 | }
75 | }
76 |
77 | private static void HandleCOMServerActivation()
78 | {
79 | Log.Information($"Activating COM Server");
80 |
81 | RepositoryCache cache = new RepositoryCache();
82 | using var gitLocalRepositoryProviderServer = new GitLocalRepositoryProviderServer();
83 | var gitLocalRepositoryProviderInstance = new GitLocalRepositoryProviderFactory(cache);
84 | gitLocalRepositoryProviderServer.RegisterGitRepositoryProviderServer(() => gitLocalRepositoryProviderInstance);
85 | gitLocalRepositoryProviderServer.Run();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/FileExplorerGitIntegration/appsettings_FileExplorerGitIntegration.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ],
4 | "MinimumLevel": "Debug",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
10 | "restrictedToMinimumLevel": "Debug"
11 | }
12 | },
13 | {
14 | "Name": "File",
15 | "Args": {
16 | "path": "%WINDOWSADVANCEDSETTINGS_LOG_ROOT%\\FileExplorerGitIntegration.dhlog",
17 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
18 | "restrictedToMinimumLevel": "Information",
19 | "rollingInterval": "Day"
20 | }
21 | },
22 | {
23 | "Name": "Debug"
24 | }
25 | ],
26 | "Enrich": [ "FromLogContext" ],
27 | "Properties": {
28 | "SourceContext": "FileExplorerGitIntegration"
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/FileExplorerSourceControlIntegration.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Exe
6 |
7 |
8 | WinExe
9 |
10 |
11 |
12 | enable
13 | false
14 | x86;x64;arm64
15 | win-x86;win-x64;win-arm64
16 | enable
17 | true
18 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Always
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/NativeMethods.txt:
--------------------------------------------------------------------------------
1 | CoRegisterClassObject
2 | CoRevokeClassObject
3 | CoResumeClassObjects
4 | MEMORYSTATUSEX
5 | GlobalMemoryStatusEx
6 | CoCreateInstance
7 | GetCurrentPackageFullName
8 |
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.Extensions.Configuration;
5 | using Microsoft.Windows.AppLifecycle;
6 | using Serilog;
7 | using Windows.ApplicationModel.Activation;
8 | using WindowsAdvancedSettings.Common.Helpers;
9 |
10 | namespace FileExplorerSourceControlIntegration;
11 |
12 | public sealed class Program
13 | {
14 | [MTAThread]
15 | public static async Task Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
16 | {
17 | // Set up Logging
18 | Environment.SetEnvironmentVariable("WINDOWSADVANCEDSETTINGS_LOG_ROOT", Path.Join(Logging.LogFolderRoot, "FileExplorerSourceControlIntegration"));
19 | var configuration = new ConfigurationBuilder()
20 | .AddJsonFile("appsettings_FileExplorerSourceControl.json")
21 | .Build();
22 | Log.Logger = new LoggerConfiguration()
23 | .ReadFrom.Configuration(configuration)
24 | .CreateLogger();
25 |
26 | Log.Information($"Launched with args: {string.Join(' ', args.ToArray())}");
27 |
28 | // Force the app to be single instanced
29 | // Get or register the main instance
30 | var mainInstance = AppInstance.FindOrRegisterForKey("mainInstance");
31 | var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
32 |
33 | if (!mainInstance.IsCurrent)
34 | {
35 | Log.Information($"Not main instance, redirecting.");
36 | await mainInstance.RedirectActivationToAsync(activationArgs);
37 | Log.CloseAndFlush();
38 | return;
39 | }
40 |
41 | if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
42 | {
43 | HandleCOMServerActivation();
44 | }
45 | else
46 | {
47 | Log.Warning("Not being launched as a ComServer... exiting.");
48 | }
49 |
50 | Log.CloseAndFlush();
51 | }
52 |
53 | private static void HandleCOMServerActivation()
54 | {
55 | Log.Information($"Activating COM Server");
56 | using var sourceControlProviderServer = new SourceControlProviderServer();
57 | var sourceControlProviderInstance = new SourceControlProvider();
58 | var wrapper = new Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer.ExtraFolderPropertiesWrapper(sourceControlProviderInstance, sourceControlProviderInstance);
59 | sourceControlProviderServer.RegisterSourceControlProviderServer(() => wrapper);
60 | sourceControlProviderServer.Run();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "FileExplorerSourceControlIntegration": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-RegisterProcessAsComServer"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/SourceControlProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Runtime.InteropServices;
5 | using FileExplorerGitIntegration.Models;
6 | using Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer;
7 | using Microsoft.Windows.DevHome.SDK;
8 | using Serilog;
9 | using Windows.Foundation.Collections;
10 | using Windows.Win32;
11 | using Windows.Win32.System.Com;
12 | using WinRT;
13 |
14 | namespace FileExplorerSourceControlIntegration;
15 |
16 | #nullable enable
17 | [ComVisible(true)]
18 | #if !DEBUG
19 | [Guid("1212F95B-257E-414e-B44F-F26634BD2627")]
20 | #else
21 | [Guid("40FE4D6E-C9A0-48B4-A83E-AAA1D002C0D5")]
22 | #endif
23 | public class SourceControlProvider :
24 | Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer.IExtraFolderPropertiesHandler,
25 | Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer.IPerFolderRootSelector
26 | {
27 | public static readonly Guid GitSourceControlGuid = typeof(GitLocalRepositoryProviderFactory).GUID;
28 |
29 | private readonly Serilog.ILogger _log = Log.ForContext("SourceContext", nameof(SourceControlProvider));
30 |
31 | public SourceControlProvider()
32 | {
33 | }
34 |
35 | public Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer.IPerFolderRootPropertyProvider? GetProvider(string rootPath)
36 | {
37 | var localRepositoryProvider = GetLocalProvider(rootPath);
38 | var result = localRepositoryProvider.GetRepository(rootPath);
39 | if (result.Result.Status == ProviderOperationStatus.Failure)
40 | {
41 | _log.Information("Could not open local repository.");
42 | _log.Information(result.Result.DisplayMessage);
43 | return null;
44 | }
45 |
46 | return new RootFolderPropertyProvider(result.Repository);
47 | }
48 |
49 | internal ILocalRepositoryProvider GetLocalProvider(string rootPath)
50 | {
51 | ILocalRepositoryProvider? provider = null;
52 | var providerPtr = IntPtr.Zero;
53 | try
54 | {
55 | var hr = PInvoke.CoCreateInstance(GitSourceControlGuid, null, CLSCTX.CLSCTX_LOCAL_SERVER, typeof(ILocalRepositoryProvider).GUID, out var extensionObj);
56 | providerPtr = Marshal.GetIUnknownForObject(extensionObj);
57 | if (hr < 0)
58 | {
59 | Log.Debug("Failure occurred while creating instance of repository provider");
60 | Marshal.ThrowExceptionForHR(hr);
61 | }
62 |
63 | provider = MarshalInterface.FromAbi(providerPtr);
64 | }
65 | finally
66 | {
67 | if (providerPtr != IntPtr.Zero)
68 | {
69 | Marshal.Release(providerPtr);
70 | }
71 | }
72 |
73 | Log.Debug("GetLocalProvider succeeded");
74 | return provider;
75 | }
76 |
77 | IDictionary IExtraFolderPropertiesHandler.GetProperties(string[] propertyStrings, string rootFolderPath, string relativePath)
78 | {
79 | var localProvider = GetLocalProvider(rootFolderPath);
80 | var localProviderResult = localProvider.GetRepository(rootFolderPath);
81 | if (localProviderResult.Result.Status == ProviderOperationStatus.Failure)
82 | {
83 | _log.Warning("Could not open local repository.");
84 | _log.Warning(localProviderResult.Result.DisplayMessage);
85 | throw new ArgumentException(localProviderResult.Result.DisplayMessage);
86 | }
87 |
88 | return GetProperties(propertyStrings, relativePath, localProviderResult.Repository);
89 | }
90 |
91 | internal static IPropertySet GetProperties(string[] properties, string relativePath, ILocalRepository repository)
92 | {
93 | var repositoryStatusPropertyString = "System.VersionControl.CurrentFolderStatus";
94 | var isFileExplorerVersionControlEnabled = true;
95 | var showFileExplorerVersionControlColumnData = true;
96 | var showRepositoryStatus = true;
97 |
98 | if (!isFileExplorerVersionControlEnabled || (!showFileExplorerVersionControlColumnData && !showRepositoryStatus))
99 | {
100 | return new PropertySet();
101 | }
102 |
103 | if (showFileExplorerVersionControlColumnData && !showRepositoryStatus)
104 | {
105 | var filteredPropertyStrings = properties.Where(s => s != repositoryStatusPropertyString).ToArray();
106 | properties = filteredPropertyStrings;
107 | }
108 | else if (!showFileExplorerVersionControlColumnData && showRepositoryStatus)
109 | {
110 | properties = [repositoryStatusPropertyString];
111 | }
112 |
113 | // Trim any string properties to 80 characters
114 | var result = repository.GetProperties(properties, relativePath);
115 | foreach (var key in result.Keys.ToList())
116 | {
117 | if (result[key] is string str)
118 | {
119 | if (str.Length > 80)
120 | {
121 | result[key] = str[..80];
122 | }
123 | }
124 | }
125 |
126 | return result;
127 | }
128 | }
129 |
130 | internal sealed class RootFolderPropertyProvider : Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer.IPerFolderRootPropertyProvider
131 | {
132 | public RootFolderPropertyProvider(ILocalRepository repository)
133 | {
134 | _repository = repository;
135 | }
136 |
137 | public IPropertySet GetProperties(string[] properties, string relativePath)
138 | {
139 | return SourceControlProvider.GetProperties(properties, relativePath, _repository);
140 | }
141 |
142 | private readonly ILocalRepository _repository;
143 | }
144 |
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/SourceControlProviderFactory`1.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Runtime.InteropServices;
5 | using WinRT;
6 |
7 | namespace COM;
8 |
9 | [ComVisible(true)]
10 | public class SourceControlProviderFactory : IClassFactory
11 | {
12 | private readonly Func _createSourceControlProvider;
13 |
14 | public SourceControlProviderFactory(Func createSourceControlProvider)
15 | {
16 | _createSourceControlProvider = createSourceControlProvider;
17 | }
18 |
19 | public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
20 | {
21 | ppvObject = IntPtr.Zero;
22 |
23 | if (pUnkOuter != IntPtr.Zero)
24 | {
25 | Marshal.ThrowExceptionForHR(CLASSENOAGGREGATION);
26 | }
27 |
28 | // TODO: How to detect between WinRT/IInspectable and COM/IUnknown interfaces
29 | ppvObject = MarshalInspectable.CreateMarshaler2(_createSourceControlProvider(), riid, true).Detach();
30 |
31 | return 0;
32 | }
33 |
34 | int IClassFactory.LockServer(bool fLock)
35 | {
36 | return 0;
37 | }
38 |
39 | private const int CLASSENOAGGREGATION = unchecked((int)0x80040110);
40 | private const int ENOINTERFACE = unchecked((int)0x80004002);
41 | }
42 |
43 | internal static class Guids
44 | {
45 | public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
46 | public const string IUnknown = "00000000-0000-0000-C000-000000000046";
47 | }
48 |
49 | // IClassFactory declaration
50 | [ComImport]
51 | [ComVisible(false)]
52 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
53 | [Guid(COM.Guids.IClassFactory)]
54 | internal interface IClassFactory
55 | {
56 | [PreserveSig]
57 | int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
58 |
59 | [PreserveSig]
60 | int LockServer(bool fLock);
61 | }
62 |
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/SourceControlProviderServer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Diagnostics;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Runtime.InteropServices;
7 | using COM;
8 | using Serilog;
9 | using Windows.Win32;
10 | using Windows.Win32.System.Com;
11 |
12 | namespace FileExplorerSourceControlIntegration;
13 |
14 | public sealed class SourceControlProviderServer : IDisposable
15 | {
16 | private readonly HashSet _registrationCookies = new();
17 | private readonly Serilog.ILogger _log = Log.ForContext("SourceContext", nameof(SourceControlProviderServer));
18 |
19 | [UnconditionalSuppressMessage(
20 | "ReflectionAnalysis",
21 | "IL2050:COMCorrectness",
22 | Justification = "SourceControlProviderFactory and all the interfaces it implements are defined in an assembly that is not marked trimmable which means the relevant interfaces won't be trimmed.")]
23 | public void RegisterSourceControlProviderServer(Func createSourceControlProviderServer)
24 | {
25 | var clsid = typeof(SourceControlProvider).GUID;
26 |
27 | _log.Debug($"Registering class object:");
28 | _log.Debug($"CLSID: {clsid:B}");
29 | _log.Debug($"Type: {typeof(T)}");
30 |
31 | uint cookie;
32 | var hr = PInvoke.CoRegisterClassObject(
33 | clsid,
34 | new SourceControlProviderFactory(createSourceControlProviderServer),
35 | CLSCTX.CLSCTX_LOCAL_SERVER,
36 | Ole32.REGCLS_MULTIPLEUSE | Ole32.REGCLS_SUSPENDED,
37 | out cookie);
38 |
39 | if (hr < 0)
40 | {
41 | Marshal.ThrowExceptionForHR(hr);
42 | }
43 |
44 | _registrationCookies.Add(cookie);
45 | _log.Debug($"Cookie: {cookie}");
46 | hr = PInvoke.CoResumeClassObjects();
47 | if (hr < 0)
48 | {
49 | Marshal.ThrowExceptionForHR(hr);
50 | }
51 | }
52 |
53 | public void Run()
54 | {
55 | // TODO : We need to handle lifetime management of the server.
56 | // For details around ref counting and locking of out-of-proc COM servers, see
57 | // https://docs.microsoft.com/windows/win32/com/out-of-process-server-implementation-helpers
58 | // https://github.com/microsoft/devhome/issues/645
59 | Console.ReadLine();
60 | var disposedEvent = new ManualResetEvent(false);
61 | disposedEvent.WaitOne();
62 | }
63 |
64 | public void Dispose()
65 | {
66 | _log.Debug($"Revoking class object registrations:");
67 | foreach (var cookie in _registrationCookies)
68 | {
69 | _log.Debug($"Cookie: {cookie}");
70 | var hr = PInvoke.CoRevokeClassObject(cookie);
71 | Debug.Assert(hr >= 0, $"CoRevokeClassObject failed ({hr:x}). Cookie: {cookie}");
72 | }
73 | }
74 |
75 | private sealed class Ole32
76 | {
77 | #pragma warning disable SA1310 // Field names should not contain underscore
78 | // https://docs.microsoft.com/windows/win32/api/combaseapi/ne-combaseapi-regcls
79 | public const REGCLS REGCLS_MULTIPLEUSE = (REGCLS)1;
80 | public const REGCLS REGCLS_SUSPENDED = (REGCLS)4;
81 | #pragma warning restore SA1310 // Field names should not contain underscore
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/FileExplorerSourceControlIntegration/appsettings_FileExplorerSourceControl.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ],
4 | "MinimumLevel": "Debug",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
10 | "restrictedToMinimumLevel": "Debug"
11 | }
12 | },
13 | {
14 | "Name": "File",
15 | "Args": {
16 | "path": "%WINDOWSADVANCEDSETTINGS_LOG_ROOT%\\FileExplorerSourceControlIntegration.dhlog",
17 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
18 | "restrictedToMinimumLevel": "Information",
19 | "rollingInterval": "Day"
20 | }
21 | },
22 | {
23 | "Name": "Debug"
24 | }
25 | ],
26 | "Enrich": [ "FromLogContext" ],
27 | "Properties": {
28 | "SourceContext": "FileExplorerSourceControlIntegration"
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/AdvancedSettings.Tester.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Exe
6 | enable
7 | enable
8 | $(MSBuildProjectName)
9 | app.manifest
10 |
11 | false
12 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
13 | false
14 | x86;x64;arm64
15 | true
16 | win-x86;win-x64;win-arm64
17 |
18 |
19 |
20 | False
21 |
22 |
23 |
24 | False
25 |
26 |
27 |
28 | False
29 |
30 |
31 |
32 | False
33 |
34 |
35 |
36 | False
37 |
38 |
39 |
40 | False
41 |
42 |
43 |
44 |
45 |
46 | all
47 | runtime; build; native; contentfiles; analyzers; buildtransitive
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Always
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/LockScreenLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/LockScreenLogo.scale-200.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/StoreLogo.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/WindowsAdvancedSettings/82a91574b7eabe842bb7899bb7fdf973d3b05a62/test/AdvancedSettings.Tester/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/ConfigureFolderPath.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using FileExplorerSourceControlIntegration;
5 | using Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer;
6 | using Serilog;
7 | using Windows.Storage;
8 |
9 | namespace AdvancedSettings.Tester;
10 |
11 | public enum ProviderType
12 | {
13 | Dev,
14 | Release,
15 | }
16 |
17 | internal class ConfigureFolderPath
18 | {
19 | private static readonly string _releaseProviderGuidString = "1212F95B-257E-414e-B44F-F26634BD2627";
20 | private static readonly string _devProviderGuidString = "40FE4D6E-C9A0-48B4-A83E-AAA1D002C0D5";
21 | private static readonly Guid _releaseProvider = new(_releaseProviderGuidString);
22 | private static readonly Guid _devProvider = new(_devProviderGuidString);
23 |
24 | public static void DisplayStatus()
25 | {
26 | foreach (var folderInfo in ExtraFolderPropertiesWrapper.GetRegisteredFolderInfos())
27 | {
28 | var providerName = GetProviderName(folderInfo.HandlerClsid);
29 | Console.WriteLine($"{providerName}: {folderInfo.RootFolderPath} {{{folderInfo.HandlerClsid}}} {folderInfo.AppId}");
30 | }
31 | }
32 |
33 | public static void AddPath(string provider, string path)
34 | {
35 | try
36 | {
37 | var providerType = (ProviderType)Enum.Parse(typeof(ProviderType), provider, true);
38 | AddPath(providerType, path);
39 | }
40 | catch (Exception ex)
41 | {
42 | Log.Error(ex, $"Invalid provider: {provider}");
43 | }
44 | }
45 |
46 | public static void AddPath(ProviderType providerType, string path)
47 | {
48 | var provider = GetProvider(providerType);
49 | Console.WriteLine($"Registering source folder: {path} for provider {providerType}");
50 | try
51 | {
52 | if (!Directory.Exists(path))
53 | {
54 | throw new ArgumentException($"Not a valid directory path: {path}");
55 | }
56 |
57 | var wrapperResult = ExtraFolderPropertiesWrapper.Register(path, provider);
58 | if (!wrapperResult.Succeeded)
59 | {
60 | Log.Error(wrapperResult.ExtendedError, "Failed to register folder for source control integration");
61 | }
62 | }
63 | catch (Exception ex)
64 | {
65 | Log.Error(ex, $"An exception occurred while registering folder {path}");
66 | }
67 | }
68 |
69 | public static void RemovePath(string path)
70 | {
71 | Log.Information($"Removing source folder: {path}");
72 | try
73 | {
74 | ExtraFolderPropertiesWrapper.Unregister(path);
75 | }
76 | catch (Exception ex)
77 | {
78 | Log.Error(ex, $"An exception occurred while removing the registration of folder {path}");
79 | }
80 | }
81 |
82 | private static Guid GetProvider(ProviderType providerType)
83 | {
84 | return providerType switch
85 | {
86 | ProviderType.Release => _releaseProvider,
87 | _ => _devProvider,
88 | };
89 | }
90 |
91 | private static string GetProviderName(Guid guid)
92 | {
93 | if (guid.Equals(_devProvider))
94 | {
95 | return "DEV";
96 | }
97 | else if (guid.Equals(_releaseProvider))
98 | {
99 | return "REL";
100 | }
101 | else
102 | {
103 | return "UNK";
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
18 | Windows Advanced Settings Tester
19 | Microsoft Corp
20 | Assets\StoreLogo.png
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using AdvancedSettings.Tester;
5 | using Microsoft.Extensions.Configuration;
6 | using Serilog;
7 | using Windows.Storage;
8 |
9 | internal sealed class Program
10 | {
11 | private static void Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
12 | {
13 | Environment.SetEnvironmentVariable("WINDOWSADVANCEDSETTINGSTESTER_LOG_ROOT", ApplicationData.Current.TemporaryFolder.Path);
14 | var configuration = new ConfigurationBuilder()
15 | .AddJsonFile("appsettings_tester.json")
16 | .Build();
17 |
18 | Log.Logger = new LoggerConfiguration()
19 | .ReadFrom.Configuration(configuration)
20 | .CreateLogger();
21 |
22 | if (args.Length == 0)
23 | {
24 | ConfigureFolderPath.DisplayStatus();
25 | }
26 | else if (args.Length > 2 && args[0].Equals("add", StringComparison.OrdinalIgnoreCase))
27 | {
28 | ConfigureFolderPath.AddPath(args[1], args[2]);
29 | ConfigureFolderPath.DisplayStatus();
30 | }
31 | else if (args.Length > 1 && args[0].Equals("remove", StringComparison.OrdinalIgnoreCase))
32 | {
33 | ConfigureFolderPath.RemovePath(args[1]);
34 | ConfigureFolderPath.DisplayStatus();
35 | }
36 | else
37 | {
38 | DisplayHelp();
39 | }
40 |
41 | Log.CloseAndFlush();
42 | }
43 |
44 | private static void DisplayHelp()
45 | {
46 | var help = "WASTester Usage:\n" +
47 | " WASTester.exe add \n" +
48 | " WASTester.exe remove \n" +
49 | " WASTester.exe";
50 | Console.WriteLine(help);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "MyApp": {
4 | "commandName": "MsixPackage",
5 | "commandLineArgs": "", /* Command line arguments to pass to the app. */
6 | "alwaysReinstallApp": false, /* Uninstall and then reinstall the app. All information about the app state is deleted. */
7 | "remoteDebugEnabled": false, /* Indicates that the debugger should attach to a process on a remote machine. */
8 | "allowLocalNetworkLoopbackProperty": true, /* Allow the app to make network calls to the device it is installed on. */
9 | "authenticationMode": "Windows", /* The authentication scheme to use when connecting to the remote machine. */
10 | "doNotLaunchApp": false, /* Do not launch the app, but debug my code when it starts. */
11 | "remoteDebugMachine": "", /* The name of the remote machine. */
12 | "nativeDebugging": false /* Enable debugging for managed and native code together, also known as mixed-mode debugging. */
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/README.md:
--------------------------------------------------------------------------------
1 | # WindowsAdvancedSettings Test Console
2 |
3 | This is a console project for interacting with the WindowsAdvancedSettings database and project.
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/AdvancedSettings.Tester/appsettings_tester.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ],
4 | "MinimumLevel": "Debug",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}"
10 | }
11 | },
12 | {
13 | "Name": "File",
14 | "Args": {
15 | "path": "%WINDOWSADVANCEDSETTINGSTESTER_LOG_ROOT%\\log.dhlog",
16 | "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
17 | "rollingInterval": "Day"
18 | }
19 | },
20 | {
21 | "Name": "Debug"
22 | }
23 | ],
24 | "Enrich": [ "FromLogContext" ],
25 | "Properties": {
26 | "SourceContext": "AdvancedSettings.Tester"
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/test/FileExplorerGitIntegration.UnitTest/FileExplorerGitIntegration.UnitTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FileExplorerGitIntegration.UnitTest
5 | x86;x64;arm64
6 | win-x86;win-x64;win-arm64
7 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
8 | false
9 | enable
10 | enable
11 | true
12 | true
13 | resources.pri
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/FileExplorerGitIntegration.UnitTest/GitCommandRunnerTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Diagnostics;
5 | using FileExplorerGitIntegration.Models;
6 | using LibGit2Sharp;
7 | using WindowsAdvancedSettings.Common.Helpers;
8 |
9 | namespace FileExplorerGitIntegration.UnitTest;
10 |
11 | [TestClass]
12 | public class GitCommandRunnerTests
13 | {
14 | private GitDetect GitDetector { get; set; } = new();
15 |
16 | private static string RepoPath => Path.Combine(Path.GetTempPath(), "GitTestRepository");
17 |
18 | [ClassInitialize]
19 | public static void ClassInitialize(TestContext context)
20 | {
21 | Debug.WriteLine("ClassInitialize");
22 | const string url = "http://github.com/libgit2/TestGitRepository";
23 | try
24 | {
25 | _ = Repository.Clone(url, RepoPath);
26 | }
27 | catch (NameConflictException)
28 | {
29 | // Clean stale test state and try again
30 | if (Directory.Exists(RepoPath))
31 | {
32 | // Cloning the repo leads to files that are hidden and readonly (such as under the .git directory).
33 | // Therefore, change the attribute so they can be deleted
34 | var repoDirectory = new DirectoryInfo(RepoPath)
35 | {
36 | Attributes = System.IO.FileAttributes.Normal,
37 | };
38 |
39 | foreach (var dirInfo in repoDirectory.GetFileSystemInfos("*", SearchOption.AllDirectories))
40 | {
41 | dirInfo.Attributes = System.IO.FileAttributes.Normal;
42 | }
43 |
44 | Directory.Delete(RepoPath, true);
45 | }
46 |
47 | _ = Repository.Clone(url, RepoPath);
48 | }
49 | }
50 |
51 | [ClassCleanup]
52 | public static void ClassCleanup()
53 | {
54 | Debug.WriteLine("ClassCleanup");
55 | if (Directory.Exists(RepoPath))
56 | {
57 | // Cloning the repo leads to files that are hidden and readonly (such as under the .git directory).
58 | // Therefore, change the attribute so they can be deleted
59 | var repoDirectory = new DirectoryInfo(RepoPath)
60 | {
61 | Attributes = FileAttributes.Normal,
62 | };
63 |
64 | foreach (var dirInfo in repoDirectory.GetFileSystemInfos("*", SearchOption.AllDirectories))
65 | {
66 | dirInfo.Attributes = FileAttributes.Normal;
67 | }
68 |
69 | DirectoryHelper.DeleteDirectoryWithRetries(RepoPath, true, 5, 100, false);
70 | }
71 | }
72 |
73 | [TestMethod]
74 | public void TestBasicInvokeGitFunctionality()
75 | {
76 | var isGitInstalled = GitDetector.DetectGit();
77 | if (!isGitInstalled)
78 | {
79 | Assert.Inconclusive("Git is not installed. Test cannot run in this case.");
80 | return;
81 | }
82 |
83 | var result = GitExecute.ExecuteGitCommand(GitDetector.GitConfiguration.ReadInstallPath(), RepoPath, "--version");
84 | Assert.IsNotNull(result.Output);
85 | Assert.IsTrue(result.Output.Contains("git version"));
86 | }
87 |
88 | [TestMethod]
89 | public void TestInvokeGitFunctionalityForRawStatus()
90 | {
91 | var isGitInstalled = GitDetector.DetectGit();
92 | if (!isGitInstalled)
93 | {
94 | Assert.Inconclusive("Git is not installed. Test cannot run in this case.");
95 | return;
96 | }
97 |
98 | var result = GitExecute.ExecuteGitCommand(GitDetector.GitConfiguration.ReadInstallPath(), RepoPath, "status");
99 | Assert.IsNotNull(result.Output);
100 | Assert.IsTrue(result.Output.Contains("On branch"));
101 | }
102 |
103 | [TestCleanup]
104 | public void TestCleanup()
105 | {
106 | if (File.Exists(Path.Combine(Path.GetTempPath(), "GitConfiguration.json")))
107 | {
108 | File.Delete(Path.Combine(Path.GetTempPath(), "GitConfiguration.json"));
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/test/FileExplorerGitIntegration.UnitTest/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
5 |
--------------------------------------------------------------------------------
/test/FileExplorerSourceControlIntegrationUnitTest/FileExplorerSourceControlIntegrationUnitTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DevHome.FileExplorerSourceControlIntegrationUnitTest
5 | x86;x64;arm64
6 | win-x86;win-x64;win-arm64
7 | $(SolutionDir)\src\Common\PublishProfiles\win-$(Platform).pubxml
8 | false
9 | enable
10 | enable
11 | true
12 | true
13 | resources.pri
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/FileExplorerSourceControlIntegrationUnitTest/RepositoryTrackingServiceUnitTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using FileExplorerSourceControlIntegration.Services;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 |
7 | namespace DevHome.FileExplorerSourceControlIntegrationUnitTest;
8 |
9 | [TestClass]
10 | public class RepositoryTrackingServiceUnitTest
11 | {
12 | private RepositoryTracking RepoTracker { get; set; } = new(Path.Combine(Path.GetTempPath()));
13 |
14 | private readonly string _extension = "testExtension";
15 |
16 | private readonly string _rootPath = "c:\\test\\rootPath";
17 |
18 | private readonly string _caseAlteredRootPath = "C:\\TEST\\ROOTPATH";
19 |
20 | [TestMethod]
21 | public void AddRepository()
22 | {
23 | RepoTracker.AddRepositoryPath(_extension, _rootPath);
24 | var result = RepoTracker.GetAllTrackedRepositories();
25 | Assert.IsNotNull(result);
26 | Assert.AreEqual(1, result.Count);
27 | Assert.IsTrue(result.ContainsKey(_rootPath));
28 | Assert.IsTrue(result.ContainsValue(_extension));
29 | RepoTracker.RemoveRepositoryPath(_rootPath);
30 | }
31 |
32 | [TestMethod]
33 | public void RemoveRepository()
34 | {
35 | RepoTracker.AddRepositoryPath(_extension, _rootPath);
36 | var result = RepoTracker.GetAllTrackedRepositories();
37 | Assert.AreEqual(1, result.Count);
38 | RepoTracker.RemoveRepositoryPath(_rootPath);
39 | result = RepoTracker.GetAllTrackedRepositories();
40 | Assert.AreEqual(0, result.Count);
41 | }
42 |
43 | [TestMethod]
44 | public void GetAllRepositories()
45 | {
46 | for (var i = 0; i < 5; i++)
47 | {
48 | RepoTracker.AddRepositoryPath(_extension, string.Concat(_rootPath, i));
49 | }
50 |
51 | var result = RepoTracker.GetAllTrackedRepositories();
52 | Assert.IsNotNull(result);
53 | Assert.AreEqual(5, result.Count);
54 | Assert.IsTrue(result.ContainsKey(string.Concat(_rootPath, 0)));
55 | Assert.IsTrue(result.ContainsValue(_extension));
56 |
57 | for (var i = 0; i < 5; i++)
58 | {
59 | RepoTracker.RemoveRepositoryPath(string.Concat(_rootPath, i));
60 | }
61 | }
62 |
63 | [TestMethod]
64 | public void GetAllRepositories_Empty()
65 | {
66 | var result = RepoTracker.GetAllTrackedRepositories();
67 | Assert.IsNotNull(result);
68 | Assert.AreEqual(0, result.Count);
69 | }
70 |
71 | [TestMethod]
72 | public void GetSourceControlProviderFromRepositoryPath()
73 | {
74 | RepoTracker.AddRepositoryPath(_extension, _rootPath);
75 | var result = RepoTracker.GetSourceControlProviderForRootPath(_rootPath);
76 | Assert.IsNotNull(result);
77 | Assert.AreEqual(_extension, result);
78 | RepoTracker.RemoveRepositoryPath(_rootPath);
79 | }
80 |
81 | [TestMethod]
82 | public void AddRepository_DoesNotAddDuplicateValues()
83 | {
84 | RepoTracker.AddRepositoryPath(_extension, _rootPath);
85 |
86 | // Atempt to add duplicate entry that is altered in case
87 | RepoTracker.AddRepositoryPath(_extension, _caseAlteredRootPath);
88 |
89 | // Ensure duplicate is not added and count is 1
90 | var result = RepoTracker.GetAllTrackedRepositories();
91 | Assert.IsNotNull(result);
92 | Assert.AreEqual(1, result.Count);
93 | Assert.IsTrue(result.ContainsKey(_rootPath));
94 | Assert.IsTrue(result.ContainsValue(_extension));
95 |
96 | RepoTracker.RemoveRepositoryPath(_rootPath);
97 | }
98 |
99 | [TestCleanup]
100 | public void TestCleanup()
101 | {
102 | if (File.Exists(Path.Combine(Path.GetTempPath(), "TrackedRepositoryStore.json")))
103 | {
104 | File.Delete(Path.Combine(Path.GetTempPath(), "TrackedRepositoryStore.json"));
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------