How can I create a reference for a enum declared inside a COM object to pass to a method that requires it? Specifically, there is a method SetHomeDir on a 3rd-party COM object that takes in an enum as the only parameter. The definition that parameter expects is basically:
typedef enum
{
abFalse = 0,
abTrue = 1,
abInherited = -2
} SFTPAdvBool;
It appears to be defined somewhere in the COM object, but not as an object that I can create with New-Object. I have tried the following and they all give the same MX Error: 7 response:
$obj.SetHomeDir($true)
$obj.SetHomeDir(1)
$obj.SetHomeDir([Object]1)
$obj.SetHomeDir([Int16]1)
Exception calling "SetHomeDir" with "1" argument(s): "MX Error: 7 (00000007)"
Here are the results from trying some other approaches:
PS C:\> New-Object -ComObject SFTPCOMINTERFACELib.SFTPAdvBool
New-Object : Cannot load COM type SFTPCOMINTERFACELib.SFTPAdvBool.
PS C:\> [SFTPAdvBool]::abTrue
Unable to find type [SFTPAdvBool]:
make sure that the assembly containing this type is loaded.
PS C:\> [SFTPCOMINTERFACELib.SFTPAdvBool]::abTrue
Unable to find type [SFTPCOMINTERFACELib.SFTPAdvBool]:
make sure that the assembly containing this type is loaded.
The method signature that COM exposes to PowerShell looks like this:
PS C:\> $user.SetHomeDir
MemberType : Method
OverloadDefinitions : {void SetHomeDir (SFTPAdvBool)}
TypeNameOfValue : System.Management.Automation.PSMethod
Value : void SetHomeDir (SFTPAdvBool)
Name : SetHomeDir
IsInstance : True
Note: This is running under PowerShell 2.0 on Windows Server 2008 R2, but I would consider upgrading the Windows Management Framework to a newer version if necessary.
Update: Here is a screenshot from the Visual Studio object explorer, in case that offers up any clues.
We ended up using a compiled C# interop wrapper around the COM object. I was able to specify an int as the parameter and just used a case statement to pass the correct value from the enum. As far as I can tell there isn't a way to do this directly from Powershell and requires wrapping the COM object in managed code.
We have opened a dialog with Globalscape and hopefully this will be something they address in a future release.
We can try to fool the com object by creating the enum on your own and pass it to the function:
If you can upgrade to Powershell 5 try (in Powershell - enum is a new keyword in ver 5):
Enum SFTPAdvBool
{
abFalse = 0
abTrue = 1
abInherited = -2
}
And call:
$obj.SetHomeDir([SFTPAdvBool]::abTrue)
for anything older than PS 5 you can try:
$code = #"
namespace SFTPCOMINTERFACELib {
public enum SFTPAdvBool {
abFalse = 0,
abTrue = 1,
abInherited = -2
}
}
"#
Add-Type -TypeDefinition $code -Language CSharpVersion3
And call:
$obj.SetHomeDir([SFTPCOMINTERFACELib.SFTPAdvBool]::abTrue)
This will basically create a C# enum and will add it as a type into Powershell.
PowerShell v2.0 is archaic at this point, but it shouldn't be stopping you here. SFTPAdvBool appears to be from the GlobalScape EFT Server COM API, so that's what I'm assuming.
The issue is that you need a value of type SFTPAdvBool, according to the C# scripting examples (See ConfigureUser.cs for one use). For a .Net object, you'd define that as [SFTPAdvBool]::abTrue or [SFTPCOMINTERFACELib.SFTPAdvBool]::abTrue, but I'm not sure if that will work. I've not worked with COM enums in PowerShell before. You might need New-Object -ComObject SFTPCOMINTERFACELib.SFTPAdvBool or some variant.
If nothing works, you could use VBScript, or C#, or contact GlobalScape in the hopes they join the 21st century and drop COM, or use WinSCP's .Net library... but I'm betting you can't since you're working someplace that paid for EFT Server.
There are some hints in their knowledge base that the library doesn't always work correctly out of process, so that would apply remotely, or when using the 32-bit library from a 64-bit process.
For example:
http://forums.globalscape.com/PrintTopic38567.aspx
The solution would be to use the 32-bit powershell in C:\Windows\SysWOW64\WindowsPowerShell\ If you need to call that remotely you may have to do it explicitly using the & operator.
Related
I use HtmlAgilityPack in my PowerShell script. As per the documentation my code is like:
[Reflection.Assembly]::LoadFile("d:\Apl\HtmlAgilityPack.1.11.29\lib\Net40\HtmlAgilityPack.dll")
[HtmlAgilityPack.HtmlDocument]$htmlDoc = #{}
$htmlDoc.LoadHtml($resp.content)
...
This works fine as expected as long as I use simple script with functions only (no classes). Now I have refactored my code so that the [HtmlAgilityPack.HtmlDocument] type is used within a class method.
Now the compiler complains "Unable to find type [HtmlAgilityPack.HtmlDocument].". How do I load/import the type in order to be able to use it within a class method?
I use PowerShell 7.0 in Windows 10 environment.
I have found the solution
Add-Type -path "d:\Apl\HtmlAgilityPack.1.11.29\lib\Net40\HtmlAgilityPack.dll"
$htmlDoc = New-Object HtmlAgilityPack.HtmlDocument
I'm learning Powershell by myself .There are tons of information around about the most common things, however when I'm faced with a slightly too specific thing, either I follow someone's previous steps or I'm lost. For instance, I don't know how to get help for specific methods. I'll give you my current situation as an example, however the question is about any case, not only this one.
I'm trying to automate some Internet Explorer browsing, and for that I'm using this guide.
However it only shows an example on GetElementsByTagName. I already know how to use Get-MemberSo I follow his code like this:
$ie = new-object -ComObject "InternetExplorer.Application"
$ie.silent = $true
$ie.navigate($requestUri)
while($ie.Busy) { Start-Sleep -Milliseconds 100 }
$doc = $ie.Document
After that I use Get-Member -InputObject $doc so I know all possible methods and I see the one I need: GetElementByID. I try to use it like this:
PS C:\Users\Myuser> $main=$doc.getElementById("main")
Cannot find an overload for "getElementById" and the argument count: "1".
At line:1 char:1
+ $main=$doc.getElementById("main")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
I get that it expects more than one argument, but I don't know how to get or find any documentation about it.
What's your go-to when you want to know what a function/method does and what does it expect?
PowerShell offers a very convenient way to inspect method and even constructor signatures (overloads):
Simply access the method name without parentheses, and PowerShell will show you all overloads (signatures) defined for this method; .e.g:
# Method
PS> 'foo'.ToUpper # Inspect the .ToUpper() method - NO () at the end.
OverloadDefinitions
-------------------
string ToUpper()
string ToUpper(cultureinfo culture)
# Constructors, via a type literal (`[...]`) and its pseudo static new() method:
PS> [xml]::new # Constructors for [xml] (System.Xml.XmlDocument)
OverloadDefinitions
-------------------
xml new()
xml new(System.Xml.XmlNameTable nt)
This technique generally also works for COM objects, such as in your case:
$ie = New-Object -ComObject InternetExplorer.Application
$ie.Navigate('http://example.org')
$ie.Document.getElementById # Get signatures for .getElementById()
In Windows PowerShell v5.1 on Windows 10 this yields (abridged):
OverloadDefinitions
-------------------
mshtml.IHTMLElement ... getElementById(string v)
mshtml.IHTMLElement ... DispHTMLDocument.getElementById(string v)
mshtml.IHTMLElement ... IHTMLDocument3.getElementById(string v)
Caveat: For some COM objects, the signatures are listed without parameter names in versions before PowerShell Core v7, which can make it much harder to understand what it does.
For instance, COM Automation server WScript.Shell is affected:
Windows PowerShell and PowerShell Core 6.x:
PS> (New-Object -ComObject WScript.Shell).Popup
OverloadDefinitions
-------------------
int Popup (string, Variant, Variant, Variant) # !! No parameter names.
In PowerShell Core 7.0 (preview 5):
PS> (New-Object -ComObject WScript.Shell).Popup
OverloadDefinitions
-------------------
int Popup (string Text, Variant SecondsToWait, Variant Title, Variant Type) # OK
For cmdlets, I look up the cmdlet online documentation (you can check on https://learn.microsoft.com or just google it). A shortcut to get to online documentation for most cmdlets is to use Get-Help cmdlet -Online. For example:
Get-Help Start-Process -Online
You can also use the help alias in place of Get-Help if you wish.
For .NET classes I also use https://learn.microsoft.com, but to make sure you get the right class and member, I will search for the full name including the namespace (you can see this when you pipe an object to Get-Member. From the class documentation, you can navigate to the member documentation on that class.
For WMI classes, once again I also look at https://learn.microsoft.com, searching for the WMI class name, and navigating to any member properties or functions attributed to that class.
Of course, this is all assuming you are looking for information on Microsoft constructs. If you are looking for third-party library documentation, you will need to reference that third-party's documentation.
In general, if you're not quite sure where to find API documentation on something, a good thing to try searching via any search engine is "LIBRARYNAME api documentation", where LIBRARYNAME could be either the library or class you want to find documentation for. Usually you'll find something relevant in the first few hits.
I'm trying to check if Powerhell script is running as Administrator.
After searching the web, I got some sample code that works fine.
In order to get the WindowsPrincipal object, I found two sample code as below.
First:
New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
Second:
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
The second one made me confused.
According to this page, I know that the [ ] is a cast operator.
And according to this page, PowerShell is built on the .NET Framework. In my opinion, this means that the above two PowerShell scripts can be converted to C#.
So I try it.
When I convert the first PowerShell script to C#. It work fine as below.
## First PowerShell script
New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
// C#
var wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
But when I try to convert the second PowerShell script to C#. I get compile error. The IDE tells me that WindowsIdentity cannot be cast to WindowsPrincipal.
## Second PowerShell script
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
// both C# code below cause compile error
var wp = System.Security.Principal.WindowsIdentity.GetCurrent() as System.Security.Principal.WindowsPrincipal;
var wp2 = (System.Security.Principal.WindowsPrincipal)System.Security.Principal.WindowsIdentity.GetCurrent();
Just as I tried on C#, the System.Security.Principal.WindowsIdentity type cannot be directly converted to the System.Security.Principal.WindowsPrincipal type. But why is the second PowerShell script available?
Or is the [ ] operator in the second PowerShell script not a type conversion operator?
Maybe this operator do more than just convert object type?
What's the difference between first PowerShell script and second PowerShell script?
Did I missing any other things?
TLDR: PowerShell can do Magic. C# can't do Magic.
PowerShell can juggle Chainsaws, bowling pins, and balls at the same time.
C# can only juggle balls if they are defined ahead of time. Trying to add a new Chainsaw into the juggling routine causes the juggler(compiler) to complain.
The issues are the differences between Functions, Object types, and how to cast object types.
System.Security.Principal is the base .NET library. The library can be used by C# and PowerShell.
WindowsIdentity.GetCurrent() is a function in the library.
WindowsPrincipal is an object type e.g. like string or int.
Calling WindowsIdentity.GetCurrent() returns a WindowsIdentity object, which you can then use in your code.
Since WindowsIdentity is not necessarily the object type you want to work with, we want to use a WindowsPrincipal object. Unfortunately we cannot directly cast from WindowsIdentity to WindowsPrincipal object. We have to use the WindowsPrincipal constructor.
In PowerShell, you create a new object either by the New-Object cmdlet, or by simply using a variable for the first time. This convenience is because PowerShell is a scripting language, where using a variable e.g. $a = 1 implicitly creates a new variable. e.g.
PS C:\> Get-Variable test
Get-Variable : Cannot find a variable with the name 'test'.
At line:1 char:1
+ Get-Variable test
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (test:String) [Get-Variable], ItemNotFoundException
+ FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand
PS C:\> $test = 1
PS C:\> Get-Variable test
Name Value
---- -----
test 1
Using the New-Object example:
New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
This is the right way to do it. You are creating a new object of type WindowsPrincipal, and passing the Windows Identity to the constructor.
The second method:
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
Is "wrong" because it uses casting, and earlier we stated that we cannot directly cast from WindowsIdentity to WindowsPrincipal object. So how does this work? Well, I said earlier that PowerShell does Magic, well I'll get to that in a minute. First let's see the correct C# Code:
Calling .NET Framework specific functions in C# do differ in Syntax than PowerShell. And the reason for the specific compile error you are getting is because we are trying to cast the object, which we can't.
The example:
using System.Security.Principal;
var wp = WindowsIdentity.GetCurrent() as WindowsPrincipal; // compile error
The Compiler translates it to:
WindowsIdentity.GetCurrent()
Run function GetCurrent()
as WindowsPrincipal;
Expect a return type of WindowsPrincipal <-- compile time error. The compile error is because the function does not return a type of WindowsPrincipal, instead it returns a type WindowsIdentity.
Second example is also a variation that doesn't work because it can't cast the object directly. e.g.
using System.Security.Principal;
var wp = (WindowsPrincipal) WindowsIdentity.GetCurrent(); // compile error
The Compiler translates it to:
WindowsIdentity.GetCurrent()
Run function GetCurrent() and return with the WindowsIdentity object.
(WindowsPrincipal) WindowsIdentity.GetCurrent();
Take the WindowsIdentity object and directly cast it to a WindowsPrincipal type, which it can't.
The correct C# code also needs the new keyword is:
var wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
The Compiler translates it to:
WindowsIdentity.GetCurrent()
Run function GetCurrent() and return with the WindowsIdentity object.
new WindowsPrincipal(WindowsIdentity.GetCurrent())
Create a new WindowsPrincipal object passing the WindowsIdentity object into the constructor. Which now works.
So why does the second example:
[Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
Work in PowerShell by doing something that is "wrong"? Because PowerShell is a scripting language, and is interpreted on the fly, and can use Magic, it can interpret the above and perform some Type Conversion Magic Quote:
Direct assignment. If your input is directly assignable, simply cast your input to that type.
Language-based conversion. These language-based conversions are done when the target type is void, Boolean, String, Array, Hashtable, PSReference (i.e.: [ref]), XmlDocument (i.e.: [xml]). Delegate (to support ScriptBlock to Delegate conversions), and Enum.
Parse conversion. If the target type defines a Parse() method that takes that input, use that.
Static Create conversion. If the target type defines a static ::Create() method that takes that input, use that.
Constructor conversion. If the target type defines a constructor that takes your input, use that.
Cast conversion. If the target type defines a implicit or explicit cast operator from the source type, use that. If the source type defines an implicit or explicit cast operator to the target type, use that.
IConvertible conversion. If the source type defines an IConvertible implementation that knows how to convert to the target type, use that.
IDictionary conversion. If the source type is an IDictionary (i.e.: Hashtable), try to create an instance of the destination type using its default constructor, and then use the names and values in the IDictionary to set properties on the source object.
PSObject property conversion. If the source type is a PSObject, try to create an instance of the destination type using its default constructor, and then use the property names and values in the PSObject to set properties on the source object. . If a name maps to a method instead of a property, invoke that method with the value as its argument.
TypeConverter conversion. If there is a registered TypeConverter or PSTypeConverter that can handle the conversion, do that. You can register a TypeConverter through a types.ps1xml file (see: $pshome\Types.ps1xml), or through Update-TypeData.
Basically, whenever you do a type conversion in PowerShell, it will perform Magic and try each method to convert the type dynamically on the fly for you. That is why it can handle you throwing a new Chainsaw or bowling pin at the juggler. PowerShell can interpret that we aren't trying to convert a chainsaw into a ball, and instead that a chainsaw is just another object, and we already know how to juggle objects.
This interpretation isn't a part of .NET, and so C# can't do that, and we have to explicitly define everything, and do everything correctly. This means that you can convert all C# and .NET code into valid PowerShell code, but not the other way around.
So here's what I'm trying to do - in Java, for instance, if I want to use an ArrayList<> in my program I just write
...
import java.util.ArrayList
...
And then from there on I can just write ArrayList<String> arr = new ArrayList<String>... etc.
Also, in Python I can, for instance, write something like
from numpy.random import randint
And again, in my program I can just write randint(something).
Is there an equivalent way of doing this in PowerShell? I'd like to use a System.Collections.ArrayList, but I can't find any syntax online.
Reference a namespace with a using namespace statement:
using namespace System.Collections
Then you can omit the namespace when creating a new object
$arr = New-Object ArrayList
You have to create New-Object forSystem.Collections.ArrayList :
$arr= New-Object System.Collections.ArrayList
The New-Object cmdlet creates an instance of a .NET Framework or COM
object.
After that you can use ArrayList methods :
$arr.Add('test')
I will throw this in just for fun. First, create a function:
function New-ArrayList{
New-Object System.Collections.ArrayList
}
Next create an alias:
New-Alias -Name al -Value New-ArrayList
Then use it:
$myArrayList = al
The best option is probably the one given by #laika, but you can also use Type Accelerators. These used to be easy to work with, but from PSv3, things were made a bit more difficult (probably to force you to use better techniques). Built-in accelerators include the standard .NET types such as int, string, DateTime, etc, and more obscure things such as System.Management.ManagementObject. You can get a full list like this:
[PsObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Get
To create your own, do this:
[PsObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Add('ArrayList',"System.Collections.ArrayList")
You can then use the new accelerator like this:
$arrayList = New-Object ArrayList
$arrayList.Add(1)
$arrayList.Add(2)
$arrayList.Add(3)
$arrayList
1
2
3
Note that these last only for the current session, so would need recreated everytime you wanted to use them.
I'm writing a PowerShell module. I have a Get-MyPerson function which accepts an Identity parameter, calls a web service and returns an object of type PERSON (the return type from the web service).
I'm now working on a Set-MyPerson object to update a couple of properties. What I want to be able to do is:
Set-MyPerson 1234 -GolfHandicap 22
Get-MyPerson JDoe | Set-MyPerson -GolfHandicap 22
(the latter following Get-ADUser | Set-ADUser usage)
This requires Set-MyPerson to accept a parameter of type string for the former and a parameter of type Person for the latter, using parameter sets to distinguish.
I have the basic functionality working for a string but am struggling with the parameter for Person objects.
[Parameter(ParameterSetName="Person",Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[PERSON]$Person,
won't work because PowerShell doesn't recognize PERSON (as expected):
Set-MyPerson : Unable to find type [PERSON]: make sure that the assembly containing this type is loaded.
How can I get PowerShell to recognize my PERSON class?
Do you try with [object] or [psbject] ?
My own solution, which came to me in a moment of echoey isolation, was rather more hassle than #JPBlanc's:
I used the WSDL command to generate a CSharp file:
wsdl http://server.dns.name/webservice/path/service?wsdl
Then I used the CSharp command-line compiler to create an assembly:
csc /target:library PersonService.cs
which created a DLL called PersonService.dll.
And then used:
$assemblyPath = "C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PersonModule\PersonService.dll"
Add-Type -Path $assemblyPath
to load it.