Skip to content

Commit 6bc6257

Browse files
Bruce PayetteTravisEz13
authored andcommitted
Allow .exe files to be used as IL binary modules (PowerShell#7281)
Fix for PowerShell#6741 Allow .exe files to be used as binary modules. Basically anywhere a .dll could be used with modules, you can now use a .exe file. Also did a little clean up, replacing constant strings with the StringLiteral values instead.
1 parent ec88045 commit 6bc6257

12 files changed

Lines changed: 157 additions & 52 deletions

File tree

src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,7 @@ private List<CompletionResult> GetResultForIdentifier(CompletionContext completi
14461446
StringLiterals.PowerShellDataFileExtension,
14471447
StringLiterals.PowerShellNgenAssemblyExtension,
14481448
StringLiterals.PowerShellILAssemblyExtension,
1449+
StringLiterals.PowerShellILExecutableExtension,
14491450
StringLiterals.PowerShellCmdletizationFileExtension
14501451
};
14511452
result = CompletionCompleters.CompleteFilename(completionContext, false, moduleExtensions).ToList();

src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3141,6 +3141,7 @@ private static void NativeCompletionModuleCommands(
31413141
StringLiterals.PowerShellDataFileExtension,
31423142
StringLiterals.PowerShellNgenAssemblyExtension,
31433143
StringLiterals.PowerShellILAssemblyExtension,
3144+
StringLiterals.PowerShellILExecutableExtension,
31443145
StringLiterals.PowerShellCmdletizationFileExtension
31453146
};
31463147
var moduleFilesResults = new List<CompletionResult>(CompleteFilename(context, containerOnly: false, moduleExtensions));

src/System.Management.Automation/engine/CommandInfo.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,10 @@ public virtual Version Version
212212
// Manifest module (.psd1)
213213
Module.SetVersion(ModuleIntrinsics.GetManifestModuleVersion(Module.Path));
214214
}
215-
else if (Module.Path.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase))
215+
else if (Module.Path.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
216+
Module.Path.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
216217
{
217-
// Binary module (.dll)
218+
// Binary module (.dll or .exe)
218219
Module.SetVersion(AssemblyName.GetAssemblyName(Module.Path).Version);
219220
}
220221
}

src/System.Management.Automation/engine/Modules/AnalysisCache.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ internal static ConcurrentDictionary<string, CommandTypes> GetExportedCommands(s
7070
{
7171
result = AnalyzeCdxmlModule(modulePath, context, lastWriteTime);
7272
}
73-
else if (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase))
73+
else if (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase))
74+
{
75+
result = AnalyzeDllModule(modulePath, context, lastWriteTime);
76+
}
77+
else if (extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
7478
{
7579
result = AnalyzeDllModule(modulePath, context, lastWriteTime);
7680
}
@@ -207,7 +211,15 @@ internal static bool ModuleAnalysisViaGetModuleRequired(object modulePathObj, bo
207211
return !hadFunctions || !hadAliases;
208212
}
209213

210-
if (modulePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
214+
if (modulePath.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase))
215+
{
216+
// A dll just exports cmdlets, so if the manifest doesn't explicitly export any cmdlets,
217+
// more analysis is required. If the module exports aliases, we can't discover that analyzing
218+
// the binary, so aliases are always required to be explicit (no wildcards) in the manifest.
219+
return !hadCmdlets;
220+
}
221+
222+
if (modulePath.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
211223
{
212224
// A dll just exports cmdlets, so if the manifest doesn't explicitly export any cmdlets,
213225
// more analysis is required. If the module exports aliases, we can't discover that analyzing

src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,7 +1218,8 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh)
12181218
{
12191219
if (moduleInfo.RootModuleForManifest != null)
12201220
{
1221-
if (moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase))
1221+
if (moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
1222+
moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
12221223
{
12231224
moduleInfo.SetModuleType(ModuleType.Binary);
12241225
}
@@ -1243,7 +1244,9 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh)
12431244
moduleInfo.RootModule = moduleInfo.Path;
12441245
}
12451246
}
1246-
else if (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || extension.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase))
1247+
else if (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
1248+
extension.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
1249+
extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
12471250
{
12481251
moduleInfo.SetModuleType(ModuleType.Binary);
12491252
moduleInfo.RootModule = moduleInfo.Path;
@@ -2218,6 +2221,13 @@ internal PSModuleInfo LoadModuleManifest(
22182221
WriteVerbose(loadMessage);
22192222
iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName));
22202223
fixedUpAssemblyPathList.Add(fileName);
2224+
2225+
fileName = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellILExecutableExtension, importingModule);
2226+
loadMessage = StringUtil.Format(Modules.LoadingFile, "Executable", fileName);
2227+
WriteVerbose(loadMessage);
2228+
iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName));
2229+
fixedUpAssemblyPathList.Add(fileName);
2230+
22212231
doBind = true;
22222232
}
22232233
}
@@ -4627,9 +4637,12 @@ internal string FixupFileName(string moduleBase, string name, string extension,
46274637
// Path resolution failed, use the combined path as default.
46284638
string result = combinedPath;
46294639

4630-
// For the given assembly name, the intention could be to use the assembly from TPAs or GAC (on Windows).
4631-
// So try loading the assembly using the passed-in name only, and use the assembly location if that succeeds.
4632-
if (!string.IsNullOrEmpty(originalExt) && originalExt.Equals(".dll", StringComparison.OrdinalIgnoreCase))
4640+
// For dlls, we cannot get the path from the provider.
4641+
// We need to load the assembly and then get the path.
4642+
// If the module is already loaded, this is not expensive since the assembly is already loaded in the AppDomain
4643+
if (!string.IsNullOrEmpty(extension) &&
4644+
(extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
4645+
extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)))
46334646
{
46344647
Assembly assembly = ExecutionContext.LoadAssembly(name: originalName, filename: null, error: out _);
46354648
if (assembly != null)
@@ -5804,7 +5817,9 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str
58045817
found = false;
58055818
}
58065819
}
5807-
else if (ext.Equals(".dll", StringComparison.OrdinalIgnoreCase) || ext.Equals(StringLiterals.PowerShellNgenAssemblyExtension))
5820+
else if (ext.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
5821+
ext.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase) ||
5822+
ext.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
58085823
{
58095824
module = LoadBinaryModule(false, ModuleIntrinsics.GetModuleName(fileName), fileName, null,
58105825
moduleBase, ss, options, manifestProcessingFlags, prefix, true, true, out found);

src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,9 @@ internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath
890890
StringLiterals.PowerShellCmdletizationFileExtension,
891891
StringLiterals.WorkflowFileExtension,
892892
StringLiterals.PowerShellNgenAssemblyExtension,
893-
StringLiterals.PowerShellILAssemblyExtension};
893+
StringLiterals.PowerShellILAssemblyExtension,
894+
StringLiterals.PowerShellILExecutableExtension,
895+
};
894896

895897
// A list of the extensions to check for implicit module loading and discovery, put the ni.dll in front of .dll to have higher priority to be loaded.
896898
internal static string[] PSModuleExtensions = new string[] {
@@ -899,7 +901,9 @@ internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath
899901
StringLiterals.PowerShellCmdletizationFileExtension,
900902
StringLiterals.WorkflowFileExtension,
901903
StringLiterals.PowerShellNgenAssemblyExtension,
902-
StringLiterals.PowerShellILAssemblyExtension};
904+
StringLiterals.PowerShellILAssemblyExtension,
905+
StringLiterals.PowerShellILExecutableExtension,
906+
};
903907

904908
/// <summary>
905909
/// Returns true if the extension is one of the module extensions...

src/System.Management.Automation/engine/Modules/ModuleUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ internal static IEnumerable<string> GetDefaultAvailableModuleFiles(string topDir
292292
yield return moduleFile;
293293

294294
// when finding the default modules we stop when the first
295-
// match is hit - searching in order .psd1, .psm1, .dll
295+
// match is hit - searching in order .psd1, .psm1, .dll, .exe
296296
// if a file is found but is not readable then it is an
297297
// error
298298
break;

src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,16 @@ protected override void ProcessRecord()
145145
}
146146
}
147147

148-
// RootModule can be null, empty string or point to a valid .psm1, , .cdxml, .xaml or .dll. Anything else is invalid.
148+
// RootModule can be null, empty string or point to a valid .psm1, , .cdxml, .xaml, .dll or .exe. Anything else is invalid.
149149
if (module.RootModule != null && module.RootModule != string.Empty)
150150
{
151151
string rootModuleExt = System.IO.Path.GetExtension(module.RootModule);
152152
if ((!IsValidFilePath(module.RootModule, module, true) && !IsValidGacAssembly(module.RootModule)) ||
153153
(!rootModuleExt.Equals(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase) &&
154-
!rootModuleExt.Equals(".dll", StringComparison.OrdinalIgnoreCase) &&
155-
!rootModuleExt.Equals(".cdxml", StringComparison.OrdinalIgnoreCase) &&
156-
!rootModuleExt.Equals(".xaml", StringComparison.OrdinalIgnoreCase))
154+
!rootModuleExt.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) &&
155+
!rootModuleExt.Equals(StringLiterals.PowerShellCmdletizationFileExtension, StringComparison.OrdinalIgnoreCase) &&
156+
!rootModuleExt.Equals(StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase) &&
157+
!rootModuleExt.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))
157158
)
158159
{
159160
string errorMsg = StringUtil.Format(Modules.InvalidModuleManifest, module.RootModule, filePath);
@@ -176,6 +177,7 @@ protected override void ProcessRecord()
176177
if (!IsValidFilePath(nestedModule.Name, module, true)
177178
&& !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellILAssemblyExtension, module, true)
178179
&& !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellNgenAssemblyExtension, module, true)
180+
&& !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellILExecutableExtension, module, true)
179181
&& !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellModuleFileExtension, module, true)
180182
&& !IsValidGacAssembly(nestedModule.Name))
181183
{

src/System.Management.Automation/engine/SessionStateStrings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ internal static class StringLiterals
130130
/// </summary>
131131
internal const string PowerShellNgenAssemblyExtension = ".ni.dll";
132132

133+
/// <summary>
134+
/// The file extension (including the dot) of an executable file.
135+
/// </summary>
136+
internal const string PowerShellILExecutableExtension = ".exe";
137+
133138
internal const string PowerShellConsoleFileExtension = ".psc1";
134139

135140
/// <summary>

src/System.Management.Automation/engine/Utils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,7 @@ internal static bool IsPowerShellAssembly(string assemblyName)
10581058
if (!string.IsNullOrWhiteSpace(assemblyName))
10591059
{
10601060
// Remove the '.dll' if it's there...
1061-
var fixedName = assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
1061+
var fixedName = assemblyName.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase)
10621062
? Path.GetFileNameWithoutExtension(assemblyName)
10631063
: assemblyName;
10641064

0 commit comments

Comments
 (0)