Following up on my last post regarding PowerShell Core, my open pull request with the PowerShell team has been merged and I’m ready to write about it!
I want to additionally expand on the title of this post as well as the previous. PowerShell is exciting, and any bugs or unexpected behaviors that I encounter don’t change that; what I mean by “isn’t always exciting” is that sometimes a bit of extra research (or, in this case, some bugfixes) may be required to get to the end goal of the code that I’m writing.
…Where did that item go?
When I first installed PowerShell Core, I attempted to install my PowerShell Environment to see if it was immediately compatible between versions 5.1 and 6.0. A few different items in the setup script depend on the directory
C:\psenv existing, and I saw multiple exceptions thrown stating that this directory didn’t exist. This was strange because I explicitly create the directory if it doesn’t already exist. While investigating with Windows Explorer, I realized that the
psenv directory had been created in the root of my environment repository directory despite specifying the root of
New-Item -Path "$Env:SystemDrive\" -Name "psenv" -ItemType "Directory" -Force
Upon further investigation with the
Get-Item cmdlets, I realized that any new item created with a path that includes a drive letter followed by a colon would end up in the current working directory. This can be reproduced using the steps in this GitHub issue.
Set-Location ~ New-Item -Path C:\ -Name Test -ItemType Directory -Verbose
PS C:\Users\mbobke> Get-Item C:\Test Get-Item : Cannot find path 'C:\Test' because it does not exist. At line:1 char:1 + Get-Item C:\Test + ~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\Test:String) [Get-Item], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand PS C:\Users\mbobke> Get-Item ~\Test Directory: C:\Users\mbobke Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 25/10/2017 13:51 Test
The same bug would not occur if the given path is a UNC path, nor if the path is one or more directory levels deeper than the root:
PS C:\Users\mbobke> New-Item -Path "\\$env:COMPUTERNAME\c$" -Name "psenv" -ItemType "Directory" Directory: \\MBOBKE-DT\c$ Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 4/3/2018 5:27 PM psenv PS C:\Users\mbobke> New-Item -Path "C:\temp" -Name "psenv" -ItemType "Directory" Directory: C:\temp Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 4/3/2018 5:32 PM psenv
It’s Bug-Squashin’ Time
At this point, I realized that I wasn’t crazy and this had to be a bug in PowerShell Core itself. (Trust me, I
New-Item‘d that directory more times than I’d like to admit…) I am admittedly not experienced with C#, at least beyond what you utilize writing beginner Unity3D scripts. However, I was bound and determined to fix this bug myself no matter how many Google search tabs I’d have to open or debugging sessions I’d have to start.
I cloned the PowerShell repository and opened it in Visual Studio Code, then searched the
src/ directory for anything that I could find related to
New-Item. I shortly came across this method which seemed to be either an entry point or helper method for the cmdlet in question; I placed a breakpoint there and started a debugging session. Executing
New-Item resulted in the execution pausing at that line, and I was then able to step through the code. Visual Studio Code has a nice debug variable explorer that helped me find exactly where the
Path value was being evaluated.
I paid careful attention to the
path variable while stepping into each stack of method calls and noticed that it finally began to change in
src/System.Management.Automation/namespaces/LocationGlobber.cs, so I focused my attention here. The
GetDriveRootRelativePathFromPSPath method parses the path to determine whether or not it is absolute or relative; in our case, it’s absolute, so it then strips the drive letter, colon, and the following path separator (slash) from the path. The path is now empty, and the method goes on to incorrectly determine that the path given by the user is empty and therefore uses the current working directly. To fix this, I added checks to determine if the path became empty after it had been parsed, and if so to use the root of the drive in the path as the destination path. I wrote a couple of tests, submitted a pull request, and it was merged after some refinement with the help of other contributors and the PowerShell team.
This was my first open-source contribution ever, and it is pretty cool to see that I helped make something that I’m so passionate about better!
Bonus points: I also introduced a bug with these changes where using
Set-Location to change to a drive on the machine that had been previously visited would fail to restore the previous working directory of that drive. You can find the related issue as well as the pull request where I fixed that issue below.