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
Related
(PS 5.1) I put a using namespace directive in a psm1 to shorten some code. It then no longer recognizes a custom type I defined. Without the directive, all is peachy. It seems that a using namespace directive overrides things I did not expect it to.
Example: I have a main.ps1, and two modules.
main.ps1
using module .\classes.psm1 # using modules is needed to bring in classes, Import-Module won't cut it
Import-Module .\process.psm1 -Force # force reload
$MyList = Get-List # call function in module
$MyList[0].Bar # show a result
classes.psm1
Class Foo
{
[int]$Bar
}
process.psm1
function Get-List {
$f = New-Object Foo
$f.Bar = 42
$list = [System.Collections.Generic.List[Foo]]::new()
$list.Add($f)
$list
}
This works fine. The trouble starts when I want to shorten things in process.psm1:
using namespace System.Collections.Generic
function Get-List {
$f = New-Object Foo
$f.Bar = 42
$list = [List[Foo]]::new() # I just want a shorter way to declare a list
$list.Add($f)
$list
}
This complains about not recognizing type Foo. Why? (When I bring in using module .\classes.psm1 in process.psm1, all is fine again.)
My point/question is: how does using namespace affect a module's capability to recognize other modules/files within a 'solution'? It find it rather counter-intuitive, but I am not a PS expert.
By default, PowerShell defers type name resolution until runtime - unless a script file contains at least one of the following:
A using statement
A class definition
An enum definition
Without the using namespace System.Collections.Generic statement, PowerShell has no reason to attempt resolving any type names in process.psm1 until after [Foo] has been loaded from classes.psm1 and is resolvable/visible.
With the using ... statement on the other hand, PowerShell attempts to resolve the [Foo] type parameter in [List[Foo]] at parse-time - before process.psm1 has actually been loaded into main.ps1, and so the error you see is thrown.
As you've found, adding an explicit using module reference to the module file containing [Foo] solves it, as process.psm1 then no longer depends on the callers type resolution scope (which is not accessible at parse-time)
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.
Just want to make a simple custom type from [System.Collections.ArrayList] to, say, just shorter[arrayList] or something like that and put it into a module for convenience. Looked into Add-Type but couldn't figure out if it fits and how to do it exactly. What I want to get is:
[ArrayList]<-[System.Collections.ArrayList] #Something like that
$myArList=New-Object ArrayList
$myArList.Add(1,2,3)
You're looking for a type accelerator.
[accelerators]::add('arrayList','System.Collections.ArrayList')
I would avoid using non-standard accelerators. PowerShell has good tab completion support for classes since at least v3.
So if you type [arraylTAB then it will complete the full name for you.
Ryan Bemrose brought up a great point; the [accelerators] type accelerator is not available by default, but you can create it like so:
$acc = [psobject].assembly.gettype("System.Management.Automation.TypeAccelerators")
$acc::Add('accelerators', $acc)
If you simply want to avoid re-typing System.Collections.ArrayList all the time, you can simply assign a type literal to a variable and use that:
$ListType = [System.Collections.ArrayList]
$MyArrayList = New-Object $ListType
# more code
$AnotherArrayList = New-Object $ListType
or, using the v5.0 new() constructor:
$MyArrayList = $ListType::new()
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.
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.