Powershell: ConsoleApp hosting PowerShell fails when published as single file

Created on 29 Aug 2020  路  7Comments  路  Source: PowerShell/PowerShell

Probably the wrong repo to report this and most likely I did something wrong. I get an exception when I try to create a runspace in a console application published as a single file. Runs fine when published normally.

Steps to reproduce

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.PowerShell.SDK" Version="7.1.0-preview.6" />
  </ItemGroup>
</Project>
using System;
using System.Management.Automation.Runspaces;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var rs = RunspaceFactory.CreateRunspace();
            Console.WriteLine("Hello World!");
        }
    }
}

dotnet publish -r win-x64 --self-contained false /p:PublishSingleFile=true

Expected behavior

Hello World

Actual behavior

Unhandled exception. System.TypeInitializationException: The type initializer for 'System.Management.Automation.PSVersionInfo' threw an exception.
 ---> System.ArgumentException: The path is empty. (Parameter 'path')
   at System.IO.Path.GetFullPath(String path)
   at System.Diagnostics.FileVersionInfo.GetVersionInfo(String fileName)
   at System.Management.Automation.PSVersionInfo..cctor()
   --- End of inner exception stack trace ---
   at System.Management.Automation.PSVersionInfo.get_PSVersion()
   at Microsoft.PowerShell.DefaultHost..ctor(CultureInfo currentCulture, CultureInfo currentUICulture)
   at System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace()
   at ConsoleApp1.Program.Main(String[] args)

Environment data

dotnet --info
.NET SDK (reflecting any global.json):
 Version:   5.0.100-preview.8.20417.9
 Commit:    fc62663a35

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19041
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\5.0.100-preview.8.20417.9\

Host (useful for support):
  Version: 5.0.0-preview.8.20407.11
  Commit:  bf456654f9

.NET SDKs installed:
  5.0.100-preview.8.20417.9 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.NETCore.App 5.0.0-preview.8.20407.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 5.0.0-preview.8.20411.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Area-Maintainers-Build Issue-Question

Most helpful comment

API incompatibility :

API | Note
-- | --
Assembly.Location | Returns an empty string.
Module.FullyQualifiedName | Returns a string with the value of聽聽or throws an exception.
Module.Name | Returns a string with the value of聽.
Assembly.GetFile | Throws聽IOException.
Assembly.GetFiles | Throws聽IOException.
Assembly.CodeBase | Throws聽PlatformNotSupportedException.
Assembly.EscapedCodeBase | Throws聽PlatformNotSupportedException.
AssemblyName.CodeBase | Returns聽null.
AssemblyName.EscapedCodeBase | Returns聽null.

Recommendations :

Action | Recommendation
-- | --
To access files next to the executable | use AppContext.BaseDirectory.
To find the file name of the executable | use the first element of Environment.GetCommandLineArgs().
To avoid shipping loose files entirely | consider using embedded resources.

Other considerations

  • Single-file doesn't bundle native libraries by default
  • There is an option to set a flag, IncludeNativeLibrariesForSelfExtract, to include native libraries in the single file bundle, but these files will be _extracted to a temporary directory_ in the client machine when the single file application is run
  • Managed C++ components aren't well suited for single-file deployment and we recommend that you write applications in C# or another non-managed C++ language to be single-file compatible.

Source : Single file deployment and API incompatibility

All 7 comments

I tried to compile PowerShell to single file a month ago with the same result.
I guess the exception comes from follow line:
c# string assemblyPath = typeof(PSVersionInfo).Assembly.Location;
I found 29 such patterns in PowerShell code. And I don't know whether the exception is a .Net bug or we should use another pattern/workaround.

I think the scenario (single file) has a right to exist and we could support it in next milestone.

It is a question for PowerShell Committee.

/cc @SteveL-MSFT @daxian-dbw

@iSazonov same problem occurs when trying to publish a Blazor webassembly with SMA. (Not the only one).

My initial report is not entirely correct. I claimed that a normal publish works. That's not true, at least not on my box. It works fine with a simple build in release mode from Visual Studio 2019 Preview.

dotnet publish -c release

However, as soon as I publish the solution with a runtime identifier (which is required for single file publish), the binary explodes upon startup. Publishing to win-x64 does not work.

dotnet publish -c release -r win-x64

@iSazonov We need to first understand what is the problem before having the committee involved.

@daxian-dbw My question is does PowerShell team want to support single file build scenario? If yes we can start to investigate problems in implementation of the scenario.

API incompatibility :

API | Note
-- | --
Assembly.Location | Returns an empty string.
Module.FullyQualifiedName | Returns a string with the value of聽聽or throws an exception.
Module.Name | Returns a string with the value of聽.
Assembly.GetFile | Throws聽IOException.
Assembly.GetFiles | Throws聽IOException.
Assembly.CodeBase | Throws聽PlatformNotSupportedException.
Assembly.EscapedCodeBase | Throws聽PlatformNotSupportedException.
AssemblyName.CodeBase | Returns聽null.
AssemblyName.EscapedCodeBase | Returns聽null.

Recommendations :

Action | Recommendation
-- | --
To access files next to the executable | use AppContext.BaseDirectory.
To find the file name of the executable | use the first element of Environment.GetCommandLineArgs().
To avoid shipping loose files entirely | consider using embedded resources.

Other considerations

  • Single-file doesn't bundle native libraries by default
  • There is an option to set a flag, IncludeNativeLibrariesForSelfExtract, to include native libraries in the single file bundle, but these files will be _extracted to a temporary directory_ in the client machine when the single file application is run
  • Managed C++ components aren't well suited for single-file deployment and we recommend that you write applications in C# or another non-managed C++ language to be single-file compatible.

Source : Single file deployment and API incompatibility

Actual behaviour :

Unhandled exception. System.TypeInitializationException: The type initializer for 'System.Management.Automation.PSVersionInfo' threw an exception.
 ---> System.ArgumentException: The path is empty. (Parameter 'path')
   at System.IO.Path.GetFullPath(String path)
   at System.Diagnostics.FileVersionInfo.GetVersionInfo(String fileName)
   at System.Management.Automation.PSVersionInfo..cctor()
   --- End of inner exception stack trace ---
   at System.Management.Automation.PSVersionInfo.get_PSVersion()
   at Microsoft.PowerShell.DefaultHost..ctor(CultureInfo currentCulture, CultureInfo currentUICulture)
   at System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace()
   at ConsoleApp1.Program.Main(String[] args)

https://github.com/PowerShell/PowerShell/blob/4b9b0788ed28ea6d463ce857d1ed81bd4a977a59/src/System.Management.Automation/engine/PSVersionInfo.cs#L79-L80

should be replaced by a safer way to get the product info (from the custom attributes of the assembly)

string productVersion = typeof(PSVersionInfo).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion.Trim();
Was this page helpful?
0 / 5 - 0 ratings