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.
Related
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
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();
first question here :)
So I have to create a custom CMDLet for Powershell 2.0, using visual studio 2010 express.
I have followed this seemingly simple tutorial : http://blogs.msdn.com/b/saveenr/archive/2010/03/08/how-to-create-a-powershell-2-0-module-and-cmdlet-with-visual-studio-2010-screencast-included.aspx
My code is almost the same (even tried copy pasting thier code) but after I call Import-Module "path_to_dll"
and then call Get-Module, i see my imported module, but no ExportedCommands are available.
ModuleType Name ExportedCommands
---------- ---- ----------------
Binary PowerShellCMDLetsLibrary {}
C# Code:
namespace PowerShellCMDLetsLibrary
{
[System.Management.Automation.Cmdlet(System.Management.Automation.VerbsCommon.Get,"RemedyXml")]
public class Get_RemedyXml:System.Management.Automation.PSCmdlet
{
[System.Management.Automation.Parameter(Position = 0, Mandatory = true)]
public string TicketID;
protected override void ProcessRecord()
{
...
this.WriteObject(Result.InnerXml, true);
}
Might be a blunder, I just can't see it
Two things jump out # me:
The TicketID is a field, not a Property.
The overnamedspaced attribution makes the code hard to read.
I suspect it's #1 , but I can't see enough past #2 to be sure.
Hope this Helps
Dont know if repost, but:
I found the solution. Copied my dll from UNC networkpath to local c:\
And now the command shows up.
If you are running this off a network or unc path , you must add the path to your .net trusts:
ie:
caspol -machine -addgroup 1. -url "file:\\\network\dir\\*" FullTrust -n Uniquename
HTH,
Bob
This is due to Code Access Security in .NET. By default, an assembly loaded from a network share executes with reduced privileges, whereas one loaded from local storage has no restrictions at all. Unfortunately, the Import-Module cmdlet gives no indication that it failed to import the cmdlets within a module, even when called with the -Verbose parameter.
To change the set of permissions granted to a particular network location, use the caspol.exe utility to create a new code group for that location:
caspol.exe -machine -addgroup 1.2 -url "file://server/share/directory/*" FullTrust
The 1.2 in the above command refers to the LocalIntranet code group, which will be the new code group's parent. The following command will show which code groups are defined, and can be used to display the group that you created:
caspol.exe -machine -listgroups
Note that on 32-bit Windows caspol.exe is located in %WinDir%\Microsoft.NET\Framework\CLR_VERSION\ (where, for PowerShell 2.0, CLR_VERSION is v2.0.50727), and on 64-bit Windows another copy is located in %WinDir%\Microsoft.NET\Framework64\CLR_VERSION\. The 32- and 64-bit versions each have their own security configuration file (CONFIG\security.config), so you will need to make sure you apply each security change to both versions using their respective caspol.exe.
The following command can be used to display the permissions that will be granted to a particular assembly:
caspol.exe -resolveperm "//server/share/directory/assembly.dll"
I basically copied an pasted your code. I did a Get-Module ClassLibrary2. Also TicketID does work.
ModuleType Name ExportedCommands
---------- ---- ----------------
Binary ClassLibrary2 Get-RemedyXml
using System.Management.Automation;
namespace ClassLibrary1
{
[Cmdlet(VerbsCommon.Get, "RemedyXml")]
public class Class1 : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string TicketID;
protected override void ProcessRecord()
{
WriteObject(TicketID);
}
}
}
We've a library written in c which is called by our main application which is written in c# (V 3.5).
Previous version of our c# application was in Delphi and that one was also using the same library.
The usage mode is as follows:
we load the library using DLLImport in the c# code and call the required function. That function creates a process using a external dll lying in the library folder and passes path of a text file which will be processed by the external dll.
The problem is that when this process was being done through Delphi application, everything was fine. but, now all the spaces are removed from the path of the text file and it is resulting in "file not found" error from the external dll.
Code in the c file:
`Some Work
// This routine executes the process
if (!CreateProcess (NULL, // No module name (use command line).
ProcessCommandLine, // Command line to execute, format : LibraryFolderPath\ExternalLibrary.exe Text File Path\TextFileName.txt
NULL, //
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
DirPath, // Use parent's starting directory.
&StartupInfo, // Pointer to STARTUPINFO structure.
&ProcessInfo ) // Pointer to PROCESS_INFORMATION structure.
)
{
// if fails to start application return to caller
return;
} // if !CreateProcess
The path specified as "Text File Path\TextFileName.txt" gets modified as "TextFilePath\TextFileName.txt" for the ExternalLibrary.exe
I've tried quoting the path but that didn't help.
Any specific reason for this behavior or any solution??
From the documentation:
If lpApplicationName is NULL, the
first white space–delimited token of
the command line specifies the module
name. If you are using a long file
name that contains a space, use quoted
strings to indicate where the file
name ends and the arguments begin (see
the explanation for the
lpApplicationName parameter). If the
file name does not contain an
extension, .exe is appended.
Therefore, if the file name extension
is .com, this parameter must include
the .com extension. If the file name
ends in a period (.) with no
extension, or if the file name
contains a path, .exe is not appended.
If the file name does not contain a
directory path, the system searches
for the executable file in the
following sequence.
I know that you are not working with the above mentioned parameter, but the trick might help.
I've made a c# class that I'm trying to run in PowerShell with add-Type.
I have a few of my own assemblies referenced and some from .net4. I'm using my own PowerShell host because the assemblies I'm referencing are made on the .net4 framework and the PowerShell Console doesn't support that yet.
Here is a sample of the script that i'm trying to run:
$Source = #"
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using MyOwn.Components.Utilities;
using MyOwn.Components;
using MyOwn.Entities;
public class ComponentSubtypeMustMatchTemplate
{
public static Guid ProblemId
{
get { return new Guid("{527DF518-6E91-44e1-B1E4-98E0599CB6B2}"); }
}
public static ProblemCollection Validate()
{
SoftwareStructureEntityContainer softwareEntityStructure = new SoftwareStructureEntityContainer();
if (softwareEntityStructure == null)
{
throw new ArgumentNullException("softwareStructure");
}
ProblemCollection problems = new ProblemCollection();
foreach (var productDependency in softwareEntityStructure.ProductDependencies)
{......
return problems;
}
}
"#
$refs = #("d:\Projects\MyOwn\bin\MyOwn.Components.dll",
"d:\Projects\MyOwn\bin\MyOwn.Entities.dll",
"d:\Projects\MyOwn\bin\MyOwn.OperationalManagement.dll",
"c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll",
"c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Data.Entity.dll",
"c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Data.dll")
Add-Type -ReferencedAssemblies $refs -TypeDefinition $Source
$MyProblemCollection = new-Object ComponentSubtypeMustMatchTemplate
$MyProblemCollection.Validate()
Now when i run this I get this error:
{"Method invocation failed because [ComponentSubtypeMustMatchTemplate] doesn't contain a method named 'Validate'."}
Ive also tried the last bit in different ways (found many different examples on how to do this) but they all seem to give the same error. I really have no idea how to get this working, and i can't seem to find any example's on something similar.
On a second note(for when i get this fixed) I was wondering if it was possible to be able to just load the .cs file instead of putting the c# code in the script. would be a better read and more maintainable.
Ive tried to see if I can see the methods with Get-Member -static like this:
$MyProblemCollection = New-Object ComponentSubtypeMustMatchTemplate
$MyProblemCollection | Get-Member -Static
And it does see the method there:
TypeName: ComponentSubtypeMustMatchTemplate
Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, System.Obje...
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, Sy...
Validate Method static MyOwn.ComponentSubtypeMustMatchTemplate... <--
ProblemId Property static System.Guid ProblemId {get;}
So why doesn't it work!?
#Philip
in my script it doesn't really matter if its static or non static cause it will always just be run once and the results will be used my program. But strange thing is it does seem to work if i use the static syntax (except for that i get no results at all) and if i use the non-static syntax i'll get the same error. I also tried the script in an other PowerShell console i found on the interwebs, and there i get an entirely different error :
PS C:\temp.
ps1
The following exception occurred while retrieving member "Validate": "Could not
load file or assembly 'MyOwn.Entit
ies, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fc80acb30cba5fab' or one
of its dependencies. The system cannot find the file specified."
At D:\Projects\temp.ps1:145 char:30
+ $MyProblemCollection.Validate <<<< ()
+ CategoryInfo : NotSpecified: (:) [], ExtendedTypeSystemExceptio
n
+ FullyQualifiedErrorId : CatchFromBaseGetMember
hmm nvm, it spontaneously worked! strange BUT i'm happy! =)
Your method is static, but you are trying to call it via an instance.
$MyProblemCollection = new-Object ComponentSubtypeMustMatchTemplate
Creates a new instance of ComponentSubtypeMustMatchTemplate.
$MyProblemCollection.Validate()
Calls the instance method named Validate on the object referenced by $MyProblemCollection. However, since there is no instance method named Validate, the call fails.
You can either make the methods non-static (which you would do if they were going to keep state in the object), or you can use a different powershell syntax used to call a static method:
$result = [ComponentSubtypeMustMatchTemplate]::Validate()
Remember ( if you keep these static ) that setting a static property means anything querying that property in the same process will get that value. If you are holding state, then I would advise removing the static from each declaration.