Powershell/C#: Import and Invoke Nested Modules - c#

This is how I load the nested modules in a ListBox.
public partial class Form1 : Form
{
readonly PowerShell _ps = PowerShell.Create();
public Form1()
{
InitializeComponent();
_ps.AddScript("Import-Module MyModules");
_ps.AddScript("(Get-Module MyModules).NestedModules");
Collection<PSObject> psObjects = _ps.Invoke();
foreach (var psObject in psObjects)
{
listBox1.Items.Add(psObject);
}
}
Now, if the user has selected a specific Module, I want to execute the same.
This does not seem to work [although module should be loaded and it should recognize the command] -
_ps.AddCommand(listBox1.SelectedItem.ToString()).Invoke();
Exception:
The term 'MyModule1' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
I assume that the Module would have been loaded in the memory by now and I have to just Invoke it. (Note that the Module Name and command name are same here)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Update:
Tried this too i.e. loading the module explicitly from the path ->> with no luck -
var path = ((PSModuleInfo) (((PSObject) (listBox1.SelectedItem)).ImmediateBaseObject)).Path;
path=path.Replace("system32", "SysWOW64");
_ps.AddCommand("Import-Module");
_ps.AddParameter(path);
_ps.Invoke();
Exception after Update: [Although Module is present, works perfectly fine in ISE x86 shell]
A parameter cannot be found that matches parameter name
'C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules\MyModules\MyModule1\MyModule1.psm1'.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

What does listBox1.SelectedItem contain?
If it is just the name MyModule1 is that found with $env:PSModulePath?
I suspect it is not in the module search path, but loaded with a relative path within the module MyModules. Therefore you need to pass Import-Module an complete path (or, if you can control the current directeory, a relative path) to the module's main file (.psd1, .psm1 or .dll as applicable).

Got it finally :-
Instead of doing this -
_ps.AddCommand(listBox1.SelectedItem.ToString()).Invoke();
Do this -
_ps.AddScript(listBox1.SelectedItem.ToString()).Invoke();

Related

PowerShell Types.ps1xml Can't Find Type When Importing Module

I'm having trouble importing a module that contains a manifest, binary (root module), and a type file that defines a type converter or code property that references a type identified in the binary.
When I try to import the module, I can see from verbose output that the types files are loaded before the binary module's classes are imported:
VERBOSE: Loading module from path '...\bin\Debug\Module\manifest.psd1'.
VERBOSE: Loading 'TypesToProcess' from path '...\bin\Debug\Module\Type.PS1XML'.
Before the root module is loaded, I get the following error:
Import-Module : The following error occurred while loading the extended type data file: , ...\bin\Debug\Module\Type.PS1XML(64) :
Error: Unable to find type [MyRootModule.MyItemConverter].
At line:1 char:128
+ ... odule\Manifest.psd1 | Import-Module -verbose ; $Debu ...
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Import-Module], RuntimeException
+ FullyQualifiedErrorId : FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand
The question is, how can I load types after the root module is imported (without turning the entire module into a script that loads everything by calling Import-Module and Update-TypeData for every item in the module directory)? Or what other way would I have to get around this error and import the module and types at the same time?
Here's a quick example. This module (concocted on-the-spot as an example only) has the following items:
module\manifest.psd1
module\root.dll
module\type.ps1xml
This is the content of root.dll
namespace MyRootModule {
public class MyTestItem {
public string Name {get; set;}
public int Id {get; set;}
}
public class MyItemConverter : PSTypeConverter {
// code omitted for brevity
// effectively creates a 'MyTestItem' with id '1' for string input 'one', and vice versa.
}
}
This is the relevant content of manifest.psd1
#{
RootModule = 'root.dll'
TypesToProcess = 'type.ps1xml'
}
This is the content of type.ps1xml
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>MyRootModule.MyTestItem</Name>
<TypeConverter>
<TypeName>MyRootModule.MyItemConverter</TypeName>
<TypeConverter>
</Type>
</Types>
If I load the binary module first, and then force-load the manifest or use update-typedata -PrependPath $pathToTypeFile, everything works and I can use the converter as I'd expect to be able to (i.e. [MyRootModule.MyTestItem]1 returns a new instance of [MyRootModule.MyTestItem]), which indicates that the problem has nothing to do with the converter itself.
TLDR; The powershell module is loading the types file before the binary, and since a type defined in the binary is not found at that time the import fails.
Took me some time but I'll mark the answer here in case anyone else runs into this problem. There's a member of the module manifest that I had forgotten about/never paid much attention to, RequiredAssemblies. You can mark your dll as a required assembly as well as the root module to import it before types files are loaded. (Note: it still needs listed as the root or a nested module to import cmdlets.)
As an example, updating the manifest listed above to the following fixes the issue:
#{
RootModule = 'root.dll'
TypesToProcess = 'type.ps1xml'
RequiredAssemblies = 'root.dll'
}

How to access a PSDrive from System.IO.File calls?

I'm writing a c# cmdlet which copies files from one location to another (similar to rsync). It even supports ToSession and FromSession.
I'd like it to work with PSDrives that use the Filesystem provider but it currently throws an error from System.IO.File.GetAttributes("psdrive:\path")
I'd really like to use calls from System.IO on the PSDrive.
How does something like copy-item do this?
I've performed a search for accessing PSDrives from c# and have returned no results.
This is the equivalent of my code
new-psdrive -name mydrive -psprovider filesystem -root \\aserver\ashare -Credential domain\user
$attributes=[system.io.file]::GetAttributes("mydrive:\path\")
returns
Exception calling "GetAttributes" with "1" argument(s): "The given path's format is not supported."
.NET knows nothing about PowerShell drives (and typically also has a different working directory), so conversion to a filesystem-native path is necessary:
In PowerShell code:
Use Convert-Path to convert a PowerShell-drive-based path to a native filesystem path that .NET types understand:
$attributes=[System.IO.File]::GetAttributes((Convert-Path "mydrive:\path\"))
By default (positional argument use) and with -Path, Convert-Path performs wildcard resolution; to suppress the latter, use the -LiteralPath parameter.
Caveat: Convert-Path only works with existing paths. Lifting that restriction is the subject of the feature request in GitHub issue #2993.
In C# code:
In PSCmdlet-derived cmdlets:
Use GetUnresolvedProviderPathFromPSPath() to translate a PS-drive-based path into a native-drive-based path[1] unresolved, which means that, aside from translating the drive part:
the existence of the path is not verified (but the drive name must exist)
and no wildcard resolution is performed.
Use GetResolvedProviderPathFromPSPath() to resolve a PS-drive-based path to a native-drive-based one, which means that, aside from translating the drive part:
wildcard resolution is performed, yielding potentially multiple paths or even none.
literal path components must exist.
Use the CurrentProviderLocation() method with provider ID "FileSystem" to get the current filesystem location's path as a System.Management.Automation.PathInfo instance; that instance's .Path property and .ToString() method return the PS form of the path; use the .ProviderPath property to get the native representation.
Here's a simple ad-hoc compiled cmdlet that exercises both methods:
# Compiles a Get-NativePath cmdlet and adds it to the session.
Add-Type #'
using System;
using System.Management.Automation;
[Cmdlet("Get", "NativePath")]
public class GetNativePathCommand : PSCmdlet {
[Parameter(Mandatory=true,Position=0)]
public string PSPath { get; set; }
protected override void ProcessRecord() {
WriteObject("Current directory:");
WriteObject(" PS form: " + CurrentProviderLocation("FileSystem"));
WriteObject(" Native form: " + CurrentProviderLocation("FileSystem").ProviderPath);
//
WriteObject("Path argument in native form:");
WriteObject(" Unresolved:");
WriteObject(" " + GetUnresolvedProviderPathFromPSPath(PSPath));
//
WriteObject(" Resolved:");
ProviderInfo pi;
foreach (var p in GetResolvedProviderPathFromPSPath(PSPath, out pi))
{
WriteObject(" " + p);
}
}
}
'# -PassThru | % Assembly | Import-Module
You can test it as follows:
# Create a foo: drive whose root is the current directory.
$null = New-PSDrive foo filesystem .
# Change to foo:
Push-Location foo:\
# Pass a wildcard path based on the new drive to the cmdlet
# and have it translated to a native path, both unresolved and resolved;
# also print the current directory, both in PS form and in native form.
Get-NativePath foo:\*.txt
If your current directory is C:\Temp and it happens to contain text files a.txt and b.txt, you'll see the following output:
Current directory:
PS form: foo:\
Native form: C:\Temp\
Path argument in native form:
Unresolved:
C:\Temp\*.txt
Resolved:
C:\Temp\a.txt
C:\Temp\b.txt
[1] If a PS drive (created with New-PSDrive) referenced in the input path is defined in terms of a UNC path, the resulting native path will be a UNC path too.

Current directory from a DLL invoked from Powershell wrong

I have a DLL with a static method which would like to know the current directory. I load the library
c:\temp> add-type -path "..."
...and call the method
c:\temp> [MyNamespace.MyClass]::MyMethod()
but both Directory.GetCurrentDirectory() and .Environment.CurrentDirectory get the current directory wrong...
what is the correct way to do this?
There are two possible "directories" you can have in powershell. One is the current directory of the process, available via Environment.CurrentDirectory or Directory.GetCurrentDirectory(). The other "directory" is the current location in the current Powershell Provider. This is what you see at the command line and is available via the get-location cmdlet. When you use set-location (alias cd) you are changing this internal path, not the process's current directory.
If you want some .NET library that uses the process's current directory to get the current location then you need to set it explicitly:
[Environment]::CurrentDirectory = get-location
Powershell has an extensible model allowing varying data sources to be mounted like drives in a file system. The File System is just one of many providers. You can see the other providers via get-psprovider. For example, the Registry provider allows the Windows Registry to be navigated like a file system. Another "Function" lets you see all functions via dir function:.
If the command in your DLL inherits from System.Management.Automation.PSCmdLet, the current PS location is available in SessionState.Path.
public class SomeCommand : PSCmdlet
{
protected override void BeginProcessing()
{
string currentDir = this.SessionState.Path.CurrentLocation.Path;
}
}
To get to the path without a session reference, this code seems to work. This solution I found after going through code that makes GIT autocomplete in PS GIT Completions, specifically this function here.
public class Test
{
public static IEnumerable<string> GetPath()
{
using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
ps.AddScript("pwd");
var path = ps.Invoke<PathInfo>();
return path.Select(p => p.Path);
}
}
}
Output:
PS C:\some\folder> [Test]::GetPath()
C:\some\folder

Assembly Location Changes in Runtime

I'm running coded ui automation and defined a method attribute called [ExternalDataSource()] to read a document (csv, xml...) and parse the data into some dictionaries. I'll copy it here so you can have a better insight:
[System.AttributeUsage(System.AttributeTargets.Method)]
public class ExternalDataSource : System.Attribute
{
public ExternalDataSource(string filename)
{
DirectoryInfo di = new DirectoryInfo(Assembly.GetExecutingAssembly().Location);
string file = Path.Combine(Path.GetDirectoryName(di.FullName), filename);
try
{
code
}
catch (Exception)
{
throw new UITestException("Cannot load data source document");
}
}
}
In it I try to access Assembly.GetExecutingAssembly().Location to get a file that is copied to the TestResult/Out folder. I assigned this attribute to only one TestMethod() in the whole application and while debugging, I found out that the application enters the attribute's c'tor twice. Both times the Location is different. Once it's from the bin/Debug folder, the other time it's from the TestResults/Out folder. Two questions:
Why does the debugger enter that attribute twice if I call it only once in my application?
Why does the location of the same assembly change?
Well it seems nobody had an answer, but while debugging a run from the command line using mstest.exe with the vs2012 JIT Debugger i found out a strange thing:
When putting a System.Diagnostics.Debugger.Break() in the class where this attribute is the jitter was called from MSTest.exe but when this breakpoint was in the testmethod decorated with this attribute, QTAgent32.exe was called. I had implemented a singleton class to handle my parameters, and while it was populated in ExternalDataSource in this attribute by MSTest, when entering QTAgent32 (the test) it was empty.
The solution that worked for me was just to initialize that Singleton with the data on [TestInitialize()].
Hope this helps somebody.

Is this the proper way to obtain a drag and drop file for the exe application created in C#?

Pretty much exactly what the title says. I created an exe windows form program in C# and i found how to obtain the file name and path that was dragged and dropped onto the exe. Well is this the proper way to obtain the file?
public partial class Form1 : Form
{
public static String file;
public Form1()
{
foreach (String arg in Environment.GetCommandLineArgs())
{
file = arg;
}
InitializeComponent();
label1.Text = file;
}
}
}
It does work and if i run the program itself, it gives me the file path and name of the exe itself. But when dragging and dropping to the exe, it gives me the file that i dropped. Is this the proper way to do it?
In general, GetCommandLineArgs may or may not contain path to executable as first argument. But in most cases - first argument will be your exe.
So, second, third and so on arguments will contains dropped files.
Hm, thought command line passed to the main method of main class in C#, no?

Categories