psake / PowerShellBuild

Common build tasks for psake and Invoke-Build that build and test PowerShell modules
MIT License
133 stars 24 forks source link

INIT task throws an exception in Windows PowerShell #53

Closed joshooaj closed 1 year ago

joshooaj commented 3 years ago

Thanks for collecting so many PowerShell module build features in one place - I really appreciate this module and the well-thought collection of tasks/variables!

I spent a few minutes looking and didn't find any mention of the intended PowerShell version compatibility, so apologies if this has already been covered. I have a PowerShell 5.1 project that cannot be migrated to PowerShell 7, and the INIT task throws an exception when called from a Windows Powershell session.

I know it can add a lot of effort to maintain backward compatibility so I 100% understand if a conscious choice is made to forego support for Windows PowerShell / PowerShell 5.1. Since this looked to be the only barrier to using PowerShellBuild with my module I thought I'd ask.

Expected Behavior

PowerShellBuild tasks are compatible with PowerShell 5.1 as well as versions based on .NET 5+

Current Behavior

The following exception is thrown during the INIT task

Error: 9/7/2021 11:41:13 AM: 
At ~\Documents\WindowsPowerShell\Modules\PowerShellBuild\0.6.1\psakeFile.ps1:19 char:5 +     Initialize-PSBuild -UseBuildHelpers -BuildEnvironment $PSBPrefere ... +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [<<==>>] Exception: Method invocation failed because [System.IO.Path] does not contain a method named 'IsPathFullyQualified'.

Possible Solution

Out of an abundance of laziness, I switched from the IsPathFullyQualified() method to IsPathRooted() in Initialize-PSBuild.ps1 which is available in both PowerShell 5.1 and PowerShell 7. I know there's a difference between the two, so a better solution would probably involve creating a private function to reimplement the IsPathFullyQualified() functionality in a way that is compatible between 5.1 and 7+.

Steps to Reproduce (for bugs)

  1. Create a new module with Stucco
  2. Execute .\build.ps1 from a Windows PowerShell terminal

Context

I'm refactoring the build process for a PowerShell 5.1 module which is a wrapper for an SDK based on .NET Framework. I already use psake and PowerShellBuild allows me to drop a lot of boilerplate. Once I modified the Initialize-PSBuild function it worked flawlessly.

Your Environment

joshooaj commented 3 years ago

I'm a bit new to BuildHelpers and PowerShellBuild, and trying to educate myself on how the build environments are constructed to understand what kind of paths you could potentially see in $BuildEnvironment.Build.OutDir, and if it's not fully qualified, how this block of code in Initialize-PSBuild.ps1 helps.

    if ([IO.Path]::IsPathFullyQualified($BuildEnvironment.Build.OutDir)) {
        $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine($BuildEnvironment.Build.OutDir, $env:BHProjectName, $BuildEnvironment.General.ModuleVersion)
    } else {
        $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine($env:BHProjectPath, $BuildEnvironment.Build.OutDir, $env:BHProjectName, $BuildEnvironment.General.ModuleVersion)
    }

As far as I can tell, the $BuildEnvironment.Build.OutDir path is constructed in build.properties.ps1 by combining $env:BHProjectPath and 'Output'. So ideally we end up with a path pointing to a subfolder of the current path named Output. If the path is fully qualified, then ModuleOutDir becomes BHProjectPath/Output/ProjectName/ModuleVersion.

If that path is not fully qualified, I'm not sure if the resulting path is as intended in all cases. Here are some examples from my Windows system and I had similar results from my ubuntu box

BHProjectPath               IsFullyQualified ModuleOutDir
-------------               ---------------- ------------
C:\repos\MyProject                      True C:\repos\MyProject\Output\MyProject\0.1.0
~\repos\MyProject                      False ~\repos\MyProject\~\repos\MyProject\Output\MyProject\0.1.0
.\repos\MyProject                      False .\repos\MyProject\.\repos\MyProject\Output\MyProject\0.1.0
C:Documents\repos\MyProject            False C:Documents\repos\MyProject\Output\MyProject\0.1.0
repos\MyProject                        False repos\MyProject\repos\MyProject\Output\MyProject\0.1.0
../../repos/MyProject                  False ../../repos/MyProject\..\..\repos\MyProject\Output\MyProject\0.1.0 
~/repos/MyProject                      False ~/repos/MyProject\~\repos\MyProject\Output\MyProject\0.1.0

It seems like under most circumstances, if the OutDir path is not fully qualified, you end up with a pretty funky ModuleOutDir. I'm assuming there are cases where OutDir is not fully qualified, and even if not, I'm for the belt & suspenders. What if instead of using "IsPathFullyQualified" we passed the $env:BHProjectPath through Resolve-Path or [IO.Path]::GetFullPath() in build.properties.ps1 when setting $BuildEnvironment.Build.OutDir? Could we then get rid of the if-block and the .NET Core 2.1+ IsPathFullyQualified method?

joshooaj commented 2 years ago

I've solved the issue for my purposes with a minor modification and tests are passing. I don't think any sort of re-implementation of IsPathFullyQualified is necessary in this case, but I don't want to make a PR until you've had a chance to look at it.

mcurole commented 2 years ago

I've run into this same issue. My project needs to run in Windows PowerShell because it is part of a JEA implementation. My project is compatible with pwsh so for now I'll build it there and assume the tests are valid for Windows Powershell, but it would be nice to fix this issue