├── .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 | --------------------------------------------------------------------------------