Current directory from a DLL invoked from Powershell wrong - c#

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

Related

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.

How to get Current Project Directory path using C#

okay, here is the question. I have two projects one is C# Console and other is Class library.
I am accessing/calling Class library method from the console app.
There is a folder called Files within the class library project.
I need to get the path of the Class library's files folder but whenever I use
System.IO.Directory.GetCurrentDirectory();
and
Environment.CurrentDirectory;
it is giving me path of the Console project which I am using to call the method.
Above methods are giving me path like
C:\\ConsolePro\\bin\\Debug
but I need the path of Class library project
C:\\ClassLibPro\\bin\\Debug
Please advise
Once the code is compiled and running, 'Project Path' has no meaning. All you can determine are the file locations of the compiled assemblies. And you can only do what you are asking if your Console project references the built 'class library' DLL directly, rather than via a Project Reference.
Then, you can make use of Reflection to get Assembly paths like;
string path = Assembly.GetAssembly(typeof (SomeClassInOtherProject)).Location;
You should be able to use Directory.GetParent(Directory.GetCurrentDirectory()) a few times to get higher level directories and then add the path of the lib directory to the end of that.
I believe the problem is:
Since the Console project has the DLL file reference it is using DLL to call any methods.
At this time it is returning the class library projct's DLL location which is located in console project's bin directory and it doesn't know about the physical location of class library project.
so essentially it is returning the same project path. I will have to move both projects in same directory in order to solve this issue.
If you loading the class library from another assembly.
string Path = System.Reflection.Assembly.GetAssembly(typeof({LibraryClassName})).Location;
string PathToClassLibPro = Path.GetDirectoryName( Path);
Replace {LibraryClassName} with the class name of your library.
I hope I understand u corretly:
Path.GetDirectoryName(typeof(Foo.MyFooClass).Assembly.Location);
I would recommend one of two options.
If the files are small include them in the class library and stream them to a temp location when needed
Other option is to copy the files during the build to the output directory and use them that way. In cases of multiple shared projects it is best to have a common bin folder that you copy assemblies to and run from that location.
Despite i cant find a good solution i use this trick :
as long as you want to come back to your ideal path u should add Directory.GetParent() instead of ...
Directory.GetParent(...(Directory.GetParent(Directory.GetCurrentDirectory()).ToString()...).ToString()
I use the following approach to get the current project path at runtime:
public static class ProjectInfo {
public static string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
public static string projectPath = appDirectory.Substring(0, appDirectory.IndexOf("\\bin"));
}
I had this exact issue as well where I couldn't access the file in my namespace's bin/debug folder. My solution was to manipulate the string using Split() then construct a new string which is the absolute path to the json file I have in my namespace.
private static string GetFilePath()
{
const char Escape = '\\'; //can't have '\' by itself, it'll throw the "Newline in constant" error
string directory = Environment.CurrentDirectory;
string[] pathOccurences = directory.Split(Escape);
string pathToReturn = pathOccurences[0] + Escape; //prevents index out of bounds in upcoming loop
for(int i = 1; i < pathOccurences.Length; i++)
{
if (pathOccurences[i] != pathOccurences[i - 1]) //the project file name and the namespace file name are the same
pathToReturn += pathOccurences[i] + Escape;
else
pathToReturn += typeof(thisClass).Namespace + Escape; //In the one occurrence of the duplicate substring, I replace it with my class Namespace name
}
return pathToReturn + "yourFile.json";
}
I personally don't like this solution, but it was the only answer I could think of.

Powershell/C#: Import and Invoke Nested Modules

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();

Powershell Importing custom C# CMDlets, no available "ExportedCommands"

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);
}
}
}

Call function from DLL with non-static path

I have a DLL that I need to access methods from.
In most cases like this I just use [DllImport] to access methods from unmanaged assemblies, but the problem with that in this situation is that it requires the path to the DLL at instantiation time, so a constant string.
This particular DLL is one that gets installed with my application and I can't guarantee where it will be after the program is installed (I'd rather not put it somewhere static like %SystemRoot%).
So is there a way in C# that I can declare and use a method from a DLL at runtime with a variable path?
Any ideas or suggestions would be greatly appreciated!
This is a bit of hack, but since you say that you can find the path to the dll at runtime, why not copy it to your current working directory before you use any of the functions? That way, the dll will exist next to your exe and will be found by LoadLibrary. No need for any additional path in your DllImport.
The only other way to use a method from a dynamic path is to do this:
1) Do the necessary P/Invoke signatures for LoadLibrary & GetProcAddress
2) Load the library from the desired path (LoadLibrary)
3) Find the desired function (GetProcAddress)
4) Cast the pointer to a delegate Marshal.GetDelegateForFunctionPointer
5) Invoke it.
Of course, you will need to declare a delegate for each function you want to "import" in this way since you have to cast the pointer to a delegate.
Don't use a path at all. Windows uses a default method of searching for DLLs when trying to dynamically or statically load a function from it.
The exact search logic is documented at MSDN in the docs for LoadLibrary - basically, if the DLL is just used by your app, put in the same folder as your application during the install and don't worry about it. If it's a commonly used DLL, put it somewhere in the folder structure searched by LoadLibrary() and it'll get found.
I had a similar situation. I use DLLs from a SDK that is installed on the machine. I get the directory location of the DLLs from that SDKs registry key. I set the DLL location on the executing users PATH variable (only temporary modification). Basically it allows you to set a dynamic path for the DLL you want to invoke, so it don't have to be from registry. Mind that the PATH var is the last place Windows looks for DLLs. But on the other hand, it does not change the other places Windows looks for DLLs.
Example:
API i want to call, on the DLL:
[DllImport("My.DLL")]
private static extern IntPtr ApiCall(int param);
Get the registry key (you need using Microsoft.Win32;):
private static string GetRegistryKeyPath() {
string environmentPath = null;
using (var rk = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\SOMENNAME"))
{
if (rk != null)
{
environmentPath = rk.GetValue("Path(or whatever your key is)").ToString();
}
if (string.IsNullOrEmpty(environmentPath))
{
Log.Warn(
string.Format("Path not found in Windows registry, using key: {0}. Will default to {1}",
#"SOFTWARE\SOMETHING", #"C:\DefaultPath"));
environmentPath = #"C:\DefaultPath";
}
}
return environmentPath;
}
Add the path of the DLL on the PATH var (Concat() is found in Linq):
void UpdatePath(IEnumerable<string> paths){
var path = new[] { Environment.GetEnvironmentVariable("PATH") ?? "" };
path = path.Concat(paths);
string modified = string.Join(Path.PathSeparator.ToString(), path);
Environment.SetEnvironmentVariable("PATH", modified);
}
Start Using the API call:
var sdkPathToAdd = GetRegistryKeyPath();
IList<string> paths = new List<string>
{
Path.Combine(sdkPathToAdd),
Path.Combine("c:\anotherPath")
};
UpdatePath(paths);
//Start using
ApiCall(int numberOfEyes);

Categories