From d6000fdea8ed424da473b5c512f19a8db10739c7 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 8 Jan 2019 10:43:24 -0800 Subject: [PATCH] Allow Windows users in developer mode to create symlinks without elevation (#8534) --- .../namespaces/FileSystemProvider.cs | 39 ++++++++++++++++--- .../New-Item.Tests.ps1 | 24 ++++++++---- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 5e32b3d8b..aa1454128 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -2434,9 +2434,16 @@ namespace Microsoft.PowerShell.Commands private static bool WinCreateSymbolicLink(string path, string strTargetPath, bool isDirectory) { - int created = NativeMethods.CreateSymbolicLink(path, strTargetPath, (isDirectory ? 1 : 0)); - bool success = (created == 1) ? true : false; - return success; + // The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer + var flags = isDirectory ? NativeMethods.SymbolicLinkFlags.Directory : NativeMethods.SymbolicLinkFlags.File; + if (Environment.OSVersion.Version.Major == 10 && Environment.OSVersion.Version.Build >= 14972 || + Environment.OSVersion.Version.Major >= 11) + { + flags |= NativeMethods.SymbolicLinkFlags.AllowUnprivilegedCreate; + } + + int created = NativeMethods.CreateSymbolicLink(path, strTargetPath, flags); + return (created == 1) ? true : false; } private static bool WinCreateHardLink(string path, string strTargetPath) @@ -7014,10 +7021,32 @@ namespace Microsoft.PowerShell.Commands /// /// Path of the symbolic link. /// Path of the target of the symbolic link. - /// 0 for destination as file and 1 for destination as directory. + /// Flag values from SymbolicLinkFlags enum. /// 1 on successful creation. [DllImport(PinvokeDllNames.CreateSymbolicLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int CreateSymbolicLink(string name, string destination, int destinationType); + internal static extern int CreateSymbolicLink(string name, string destination, SymbolicLinkFlags symbolicLinkFlags); + + /// + /// Flags used when creating a symbolic link. + /// + [Flags] + internal enum SymbolicLinkFlags + { + /// + /// Symbolic link is a file. + /// + File = 0, + + /// + /// Symbolic link is a directory. + /// + Directory = 1, + + /// + /// Allow creation of symbolic link without elevation. Requires Developer mode. + /// + AllowUnprivilegedCreate = 2 + } /// /// Creates a hard link using the native API. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 index 596c92270..9eaf79239 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 @@ -256,19 +256,27 @@ Describe "New-Item with links" -Tags @('CI', 'RequireAdminOnWindows') { } } -Describe "New-Item with links fails for non elevated user." -Tags "CI" { +Describe "New-Item with links fails for non elevated user if developer mode not enabled on Windows." -Tags "CI" { BeforeAll { - $tmpDirectory = $TestDrive $testfile = "testfile.txt" - $testfolder = "newDirectory" $testlink = "testlink" - $FullyQualifiedFile = Join-Path -Path $tmpDirectory -ChildPath $testfile - $FullyQualifiedFolder = Join-Path -Path $tmpDirectory -ChildPath $testfolder + $FullyQualifiedFile = Join-Path -Path $TestDrive -ChildPath $testfile + $TestFilePath = Join-Path -Path $TestDrive -ChildPath $testlink + $developerMode = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock -ErrorAction SilentlyContinue).AllowDevelopmentWithoutDevLicense -eq 1 } - It "Should error correctly when failing to create a symbolic link" -Skip:(Test-IsRoot) { - # This test expects that /sbin exists but is not writable by the user - { New-Item -ItemType SymbolicLink -Path "/sbin/powershell-test" -Target $FullyQualifiedFolder -ErrorAction Stop } | + AfterEach { + Remove-Item -Path $testFilePath -Force -ErrorAction SilentlyContinue + } + + It "Should error correctly when failing to create a symbolic link and not in developer mode" -Skip:(!$IsWindows -or $developerMode -or (Test-IsElevated)) { + { New-Item -ItemType SymbolicLink -Path $TestFilePath -Target $FullyQualifiedFile -ErrorAction Stop } | Should -Throw -ErrorId "NewItemSymbolicLinkElevationRequired,Microsoft.PowerShell.Commands.NewItemCommand" + $TestFilePath | Should -Not -Exist + } + + It "Should succeed to create a symbolic link without elevation and in developer mode" -Skip:(!$IsWindows -or !$developerMode -or (Test-IsElevated)) { + { New-Item -ItemType SymbolicLink -Path $TestFilePath -Target $FullyQualifiedFile -ErrorAction Stop } | Should -Not -Throw + $TestFilePath | Should -Exist } }