Long path in powershell and .net - c#

We're executing following commands
...
[System.IO.File]::WriteAllBytes($pathA, $data)
[System.IO.Compression.ZipFile]::ExtractToDirectory($pathA, $pathB)
// OR
Expand-Archive $pathA -DestinationPath $pathB
on Windows 10 Ent. x64 in versions
Name Value
---- -----
PSVersion 5.1.17763.316
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.17763.316
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
The code works unless $pathB happens to be too long. Tried many suggestions found here
\\?\$pathB does work in Explorer but in this code fails in ExtractToDirectory with
The filename, directory name, or volume label syntax is incorrect
and in Expand-Archive with
Cannot process argument because the value of argument "drive" is null.
It shouldn't be a matter of backslash escaping, if I doubled them it gave me
Illegal characters in path
Also tried New-PSDrive as in
New-PSDrive -Name "X" -PSProvider FileSystem -Root $pathBase
but I'm getting
A drive with the name 'X' does not exist.
I read that it needs -Persist, which gives me
...the root must be a file system location on a remote computer.
Tried
net use x: $pathBase
// OR
subst $pathBase "x:\"
Join-Path -Path "x:\" -ChildPath ...
and both end in
Cannot find drive. A drive with the name 'x' does not exist.
Are any of these used wrong? If not - what else to try to fix this?
Thank you

Related

EnumerateFiles Method, UnauthorizedAccessException, and compiling C# in PowerShell [duplicate]

This works to count *.jpg files.
PS C:\> #([System.IO.Directory]::EnumerateFiles('C:\Users\Public\Pictures', '*.jpg', 'AllDirectories')).Count
8
How can an -ErrorAction Continue be applied to this?
PS C:\> #([System.IO.Directory]::EnumerateFiles('C:\Users', '*.jpg', 'AllDirectories')).Count
An error occurred while enumerating through a collection: Access to the path 'C:\Users\Administrator' is denied..
At line:1 char:1
+ #([System.IO.Directory]::EnumerateFiles('C:\Users', '*.jpg', 'AllDire ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I don't think you can. Unless you want to implement directory traversal yourself you're probably stuck with something like this:
Get-ChildItem 'C:\Users' -Filter '*.jpg' -Recurse -Force -ErrorAction SilentlyContinue
Ansgar Wiechers' helpful answer shows a workaround using Get-ChildItem, which is necessary when using the full, Windows-only .NET Framework (FullCLR), on which Windows PowerShell is built.
By contrast, .NET Core v2.1+ - on which PowerShell Core is built - does offer a solution:
#([System.IO.Directory]::EnumerateFiles(
'C:\Users',
'*.jpg',
[System.IO.EnumerationOptions] #{
IgnoreInaccessible = $true
RecurseSubDirectories = $true
}
)).Count
Note that this is the equivalent of -ErrorAction Ignore, not Continue (or SilentlyContinue), in that inaccessible directories are quietly ignored, with no way to examine which of them were inaccessible afterwards.
The solution above is based on this System.IO.Directory.EnumerateFiles() overload, which offers a System.IO.EnumerationOptions parameter.
The above answers work around the issue. They donnot appy the error action.
To realy catch the error action in the .net call, I'm using the $ErrorActionPreference variable in Windows PowerShell, as descirbed in https://devblogs.microsoft.com/scripting/handling-errors-the-powershell-way/:
# Store $ErrorActionPreference
$OldErrorActionPreference = $ErrorActionPreference
# Set $ErrorActionPreference for .net action
# see https://devblogs.microsoft.com/scripting/handling-errors-the-powershell-way/ for other values
$ErrorActionPreference = 'SilentlyContinue'
# .net call
#([System.IO.Directory]::EnumerateFiles('C:\Users\Public\Pictures', '*.jpg', 'AllDirectories')).Count
# restore origional $ErrorActionPreference
$ErrorActionPreference = $OldErrorActionPreference

How can I get the `Suggestion` of PowerShell's Get-Command in C#?

If I create a file called "dir.exe" and run PowerShell command Get-Command dir -Type Application, I get and error because dir is not an application (although that file exists):
gcm : The term 'dir' 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.
At line:1 char:2
+ (gcm dir -Type Application)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (dir:String) [Get-Command], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand
Suggestion [3,General]: The command dir was not found, but does exist in the current location. Windows PowerShell does not load commands from the current location by default. If you trust this command, instead type: ".\dir". See "get-help about_Command_Precedence" for more details.
Notice the Suggestion at the bottom: Suggestion [3,General]: The command dir was not found, but does exist in the current location. Windows PowerShell does not load commands from the current location by default. If you trust this command, instead type: ".\dir". See "get-help about_Command_Precedence" for more details.
I'm trying to catch that suggestion in my C# code:
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Helpers.Tests {
[TestClass]
public class PowerShellRunner_Tests {
[TestMethod]
public void GetCommand_Test() {
// create file called "dir.exe" to see how PowerShell handles
// "Get-Command dir -Type Application":
File.Create("dir.exe").Dispose();
using (PowerShell powerShell = PowerShell.Create()) {
powerShell.AddCommand("get-command")
.AddArgument("dir")
.AddParameter("Type", CommandTypes.Application.ToString());
// run "Get-Command dir -Type Application":
CommandInfo commandInfo = powerShell.Invoke<CommandInfo>().FirstOrDefault();
// get the error:
ErrorRecord error = powerShell.Streams.Error.FirstOrDefault();
// emit the "Suggestion":
Trace.WriteLine(error.ErrorDetails.RecommendedAction);
}
}
}
}
However error.ErrorDetails is null. How can I get that Suggestion?
(I'm trying to get the behavior of where.exe but without the hassle of running a whole process for that).
Given that the end goal is to emulate where.exe's behavior, try the following:
(Get-Command -Type Application .\dir, dir -ErrorAction Ignore).Path
Note the use of -Type Application to limit results to executables and exclude PowerShell-internal commands such as function and aliases.
This will look in the current directory first, as where.exe does.
Give a mere name such as dir, Get-Command doesn't look in the current directory, because PowerShell does not permit invoking executables located in the current directory by name only - for security reasons; using relative path .\, however, makes Get-Command find such an executable.
From cmd.exe, however - whose behavior where.exe assumes - invoking a current-directory-only dir.exe with just dir (by name only) works fine.
If the output is just one path, and that path is a file in the current directory, you can infer that the dir executable exists only in the current directory, which is the condition under which PowerShell emits the suggestion to use an explicit path on invocation.
$fullPaths = (Get-Command -Type Application .\dir, dir -ErrorAction Ignore).Path
$emitSuggestion = $fullPaths.Count -eq 1 -and
(Test-Path ('.\' + (Split-Path -Leaf $fullPaths[0]))
Note: Strictly speaking, you'd also to have rule out the case where the current directory just so happens be one that is listed in $env:PATH:
$env:PATH -split ';' -ne '' -notcontains (Split-Path -Parent $fullPaths[0])
You can report that to your C# code by writing a custom version of the suggestion to the error stream via Write-Error, or, preferably, to the warning stream, with Write-Warning.
To use the above commands via the PowerShell SDK, it's simplest to use the .AddScript() method; e.g.:
powerShell.AddScript("(Get-Command -Type Application .\dir, dir -ErrorAction Ignore).Path");
As for capturing or silencing PowerShell's suggestions:
Unfortunately, you cannot gain access to suggestions programmatically (written as of Windows PowerShell v5.1 / PowerShell Core 6.1.0):
Using the PowerShell SDK, as you do, involves the PowerShell default host, which fundamentally doesn't emit suggestions.
It is only the console host, as used in console (terminal) windows that emits suggestions, but even there suggestions are printed directly to the screen, bypassing PowerShell's system of output streams.
In short: Suggestions only show in console windows (terminals), and can only be viewed, not captured there.
A quick demonstration of the behavior of suggestions in a console window (assumes Windows, with a file named dir.exe in the current dir and not also in $env:PATH):
PS> & { try { Get-Command dir.exe } catch {} } *>$null
Suggestion [3,General]: The command dir.exe was not found, but does exist in the current location. Windows PowerShell does not load commands from the current location by default. If you trust this command, instead type: ".\dir.exe". See "get-help about_Command_Precedence" for more details.
As you can see, despite the attempt to suppress all output (*>$null), the suggestion still printed to the screen, which also implies that you cannot capture suggestions.
However, there is a way to silence suggestions, namely with -ErrorAction Ignore (PSv3+); by contrast, with -ErrorAction SilentlyContinue the suggestion still prints(!):
PS> & { try { Get-Command dir.exe -ErrorAction Ignore } catch {} } *>$null
# no output

Loading a Powershell Module from the C# code of a custom Provider

I've been working on a VERY specific functionality "need" to tie into a custom Provider I'm writing in C#.
Basically I set out to find a way to replicate the
A:
B:
etc functions defined when PowerShell loads so instead of having to type
CD A:
You can just do the aforementioned
A:
I tried first to have my provider inject the functions into the runspace but it seems I'm completely missing the timing of how to get that to work so I went another route.
Basically I have a VERY simple PSM1 file UseColons.psm1
function Use-ColonsForPSDrives
{
[CmdletBinding()] Param()
Write-Verbose "Looping Through Installed PowerShell Providers"
Get-PSProvider | % `
{
Write-Verbose "Found $($_.Name) checking its drives"
$_.Drives | ? { (Get-Command | ? Name -eq "$($_.Name):") -eq $null } | `
{
Write-Verbose "Setting up: `"function $($_.Name):() {Set-Location $($_.Name):}`""
if ($Verbose)
{
. Invoke-Expression -Command "function $($_.Name):() {Set-Location $($_.Name):}"
}
else
{
. Invoke-Expression -Command "function $($_.Name):() {Set-Location $($_.Name):}" -ErrorAction SilentlyContinue
}
Write-Verbose "Finished with drive $($_.Name)"
}
}
# Cert and WSMan do not show up as providers until you try to naviagte to their drives
# As a result we will add their functions manually but we will check if they are already set anyways
if ((Get-Command | ? Name -eq "Cert:") -eq $null) { . Invoke-Expression -Command "function Cert:() {Set-Location Cert:}" }
if ((Get-Command | ? Name -eq "WSMan:") -eq $null) { . Invoke-Expression -Command "function WSMan:() {Set-Location WSMan:}" }
}
. Use-ColonsForPSDrives
In simple terms it loops through all loaded providers, then through all the drives of each provider, then it checks if the Function: drive contains a function matching the {DriveName}: format and if one is not found it creates one.
The psd1 file is nothing more than export all functions
This is stored in the %ProgramFiles%\WindowsPowerShell\Modules path under its own folder
And finally I have profile.ps1 under the %windir%\system32\windowspowershell\v1.0 directory that just does
Remove-Module UseColons -ErrorAction SilentlyContinue
Import-Module UseColons
So when I load PowerShell or the ISE if I want to get to say dir through the variables I can just call
Variable:
Or if I need to switch back to the registry
HKLM:
HKCU:
Which when you are working with multiple providers typing that CD over and over as you switch is just annoying.
Now to the problem I'm still working on developing the actual PowerShell provider this was originally intended for. But when I debug it the UseColons module loads BEFORE visual studio turns around and loads the new provider so if I manually remove and import the module again it does its thing and I have all my drive functions for my provider.
I wanted to know after that LONG explanation how can I either:
Setup my UseColons module to load LAST
Find a way to have my Custom Provider (technically a module since it has the provider AND custom Cmdlets) load the UseColons module when it initializes
I don't want to remove it from my standard profile because it is very helpful when I'm not working on the new provider and just tooling around using powershell for administrative stuff.
Hopefully someone can give me some ideas or point me in the direction of some good deeper dive powershell provider documentations and how-tos.
In your module manifest (.psd1), you have a DLL as the RootModule?
This is a horrible hack, and does not help for drives that get created in the future, but...
In your module manifest, instead of YourProvider.dll as the RootModule, use Dummy.psm1 instead (can be an empty file). Then, for NestedModules, use #( 'YourProvider.dll', 'UseColons' ). This allows the UseColons module to be loaded after YourProvider.dll. (Dummy will be last.)

Compiling C# Code with Coderunner — No Warnings Allowed

I would love to have a little problem solved. I know that it's not the best way to debug a piece of code with possible warnings, but I love to debug all the time when I have a little break between to ideas. I just found out about mono and the possibility to compile C# code running on Mac OS X Mountain Lion. I integrated it in the CodeRunner app, and it works without any problems. However, if there appears a warning in the code, it does not work.
For example, I tried to compile a code that creates one integer (nothing more than that) and it was not debugging because of that warning. I'm getting this error message:
Untitled.cs(9,29): warning CS0219: The variable `test' is assigned but its value is never used
Cannot open assembly 'Compilation succeeded - 1 warning(s)
Untitled.exe': No such file or directory.
Someone may know how to deal with that. I know it's not an essential feature, but I would love to debug the code even with some unused variables.
The content of the compilation script file:
#!/bin/bash
enc[4]="UTF8" # UTF-8
enc[10]="UTF16" # UTF-16
enc[5]="ISO8859-1" # ISO Latin 1
enc[9]="ISO8859-2" # ISO Latin 2
enc[30]="MacRoman" # Mac OS Roman
enc[12]="CP1252" # Windows Latin 1
enc[3]="EUCJIS" # Japanese (EUC)
enc[8]="SJIS" # Japanese (Shift JIS)
enc[1]="ASCII" # ASCII
rm -rf "$4"/csharp-compiled
mkdir "$4"/csharp-compiled
#mcs is the Mono CSharp Compiler
file="$1"
length=${#file}
first=`expr $length - 3`
classname=`echo "$file" | cut -c 1-"$first"`
#echo -out:"$4"/csharp-compiled/"$classname".exe "$1"
dmcs -out:"$4"/csharp-compiled/"$classname".exe "$1"
status=$?
if [ $status -ne 0 ]
then
exit $status
fi
#echo "$4"/csharp-compiled/
currentDir="$PWD"
cd "$4"/csharp-compiled/
files=`ls -1 *.exe`
status=$?
if [ $status -ne 0 ]
then
exit 1
fi
cd "$currentDir"
for file in $files
do
mv -f "$4"/csharp-compiled/$file "$file"
done
# Otherwise output the name of the input file without extension (this should be the same as the class name)
file="$1"
length=${#file}
first=`expr $length - 3`
classname=`echo "$file" | cut -c 1-"$first"`
echo $classname".exe"
exit 0
dmcs -out:"$4"/csharp-compiled/"$classname".exe "$1"
dmcs puts some messages on stdout, and some on stderr. CodeRunner expects stdout to only contain the output file name, nothing else, so to make that happen, >&2 can be used to redirect everything else to stderr.
dmcs -out:"$4"/csharp-compiled/"$classname".exe "$1" >&2
This worked for me on Sierra with current Mono and CodeRunner versions:
Language compile script:
file=$CR_FILENAME
/Library/Frameworks/Mono.framework/Versions/Current/bin/mcs "$CR_FILENAME" >&2
status=$?
if [ $status -ne 0 ]
then
exit $status
fi
echo $file | sed -e "s/\.cs/.exe/"
exit 0
Run command:
PATH="/Library/Frameworks/Mono.framework/Versions/Current/bin:$PATH"; mono $compiler
In MS compiler thre is a way to hide warnings
Pragma Warning Preprocessor Directive
#pragma warning disable 219
var test = "";
#pragma warning restore 219
It might help you.

Gems with .NET Applications - How do I set up the Executables so they run without error?

I have a gem, roundhouse, which is an application compiled with .NET (C#). Runs on Windows and it should run in a 32 bit process.
To set up my gemspec, I set:
Gem::Specification.new do |s|
s.platform = 'mswin32'
s.name = 'roundhouse'
s.version = version
s.files = Dir['lib/**/*'] + Dir['bin/**/*']
s.bindir = 'bin'
s.executables << 'rh.exe'
When I install the gem, I should be able to type rh.exe from the command line at any path and it should run correctly.
In practice, I'm not seeing this work correctly. This is what I'm getting back:
Window has this for the header: 16 bit MS-DOS Subsystem
C:\WINDOWS\system32\cmd.exe - rh.exe
The NTVDM CPU has encountered an illegal instruction.
CS:xxxx IP:xxxx OP:xx xx xx xx xx Choose 'Close' to terminate the application.
Here is a picture of the issue (link to TwitPic): Error
If I go to the directory where the item was installed, I can run it and it works great. It's just something in the registration of the command to run from anywhere.
I did quite a bit of searching before asking and came up with nothing. It could be that I don't know what I should be searching for. So let me ask the question, is there a way to register an executable with gems for windows executable applications (built with .NET) and have them register properly with the command line? If so, how is that done?
UPDATE:
I found that gems creates a shim in the C:\Ruby\bin directory that points back to the other file. So there is a rh.exe file that is really just a text file. This is its contents:
#!C:/Ruby/bin/ruby.exe
#
# This file was generated by RubyGems.
#
# The application 'roundhouse' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem 'roundhouse', version
load Gem.bin_path('roundhouse', 'rh.exe', version)
if you're distributing it with the file "rh.exe"
then you'll want to create a file
bin/rh
s.executables << 'bin/rh'
then when it's installed gems will create an "rh.bat" file which runs ruby "bin/rh" essentially (as you've seen).
So within bin/rh put something like
result = system(File.dirname(__FILE__) + "/rh.exe" ARGV.join(' '))
exit 1 unless result
result = system(File.dirname(__FILE__) + "/rh.exe " + ARGV.join(' '))
exit 1 unless result
So the endresult should maybe look like? note the space after 'rh.exe'

Categories