Getting target framework attribute in PowerShell Core - powershell

I'm looking for a way to retrieve the target framework attribute (e.g. .NETCoreApp,Version=v2.1) from a DLL when using PowerShell Core, ideally without loading the DLL directly into the main session.
I can do this in Windows PowerShell 5, as it has access to the ReflectionOnlyLoadFrom method...
$dllPath = 'C:\Temp\ADALV3\microsoft.identitymodel.clients.activedirectory.2.28.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll'
[Reflection.Assembly]::ReflectionOnlyLoadFrom($dllPath).CustomAttributes |
Where-Object {$_.AttributeType.Name -eq 'TargetFrameworkAttribute'} |
Select -ExpandProperty ConstructorArguments |
Select -ExpandProperty value
However, I realise that this approach isn't available in .NET Core.
Editor's note: Even though the documentation (as of this writing) misleadingly suggests that the ReflectionOnlyLoadFrom method is available in .NET Core, it is not, as explained here.
From what I've seen, it looks likely that I should be able to access the custom attributes that hold the target framework attribute by using an instance of the System.Reflection.Metadata.MetadataReader class that's available in .NET Core (a couple of examples of this in use can be found here: https://csharp.hotexamples.com/examples/System.Reflection.Metadata/MetadataReader/GetCustomAttribute/php-metadatareader-getcustomattribute-method-examples.html ). However, all the constructors for this type seem to use a Byte* type, as the following shows when running from PowerShell Core:
([type] 'System.Reflection.Metadata.MetadataReader').GetConstructors() | % {$_.GetParameters() | ft}
I have no idea how to create a Byte* type in any version of PowerShell. Perhaps there's a method in System.Reflection.Metadata that I should be using before creating the MetadataReader object, but I haven't found it yet.
Apologies for the length of this question, but I'm hoping by sharing my notes I'll help in tracking down the solution. Any advice on how this target framework information can be obtained using PowerShell Core?

After quite a bit of work, I managed to put together a PowerShell script that works (without external dependencies) in PowerShell Core that pulls in the target framework from a DLL:
$dllPath = 'C:\Temp\ADALV3\microsoft.identitymodel.clients.activedirectory.2.28.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll'
$stream = [System.IO.File]::OpenRead($dllPath)
$peReader = [System.Reflection.PortableExecutable.PEReader]::new($stream, [System.Reflection.PortableExecutable.PEStreamOptions]::LeaveOpen -bor [System.Reflection.PortableExecutable.PEStreamOptions]::PrefetchMetadata)
$metadataReader = [System.Reflection.Metadata.PEReaderExtensions]::GetMetadataReader($peReader)
$assemblyDefinition = $metadataReader.GetAssemblyDefinition()
$assemblyCustomAttributes = $assemblyDefinition.GetCustomAttributes()
$metadataCustomAttributes = $assemblyCustomAttributes | % {$metadataReader.GetCustomAttribute($_)}
foreach ($attribute in $metadataCustomAttributes) {
$ctor = $metadataReader.GetMemberReference([System.Reflection.Metadata.MemberReferenceHandle]$attribute.Constructor)
$attrType = $metadataReader.GetTypeReference([System.Reflection.Metadata.TypeReferenceHandle]$ctor.Parent)
$attrName = $metadataReader.GetString($attrType.Name)
$attrValBytes = $metadataReader.GetBlobContent($attribute.Value)
$attrVal = [System.Text.Encoding]::UTF8.GetString($attrValBytes)
if($attrName -eq 'TargetFrameworkAttribute') {Write-Output "AttributeName: $attrName, AttributeValue: $attrVal"}
}
$peReader.Dispose()
I'm mostly happy with it, the only issue I'd still like to sort out is that I'm getting some unhandled characters in the string output. I'll try to get rid of them.

Related

Exposing the Connection token from Connect-AzureAd

I am using the AzureAd Powershell module for user management. However it does not have all the functionality that I need, specifically, I can't assign Application Extension values to objects, (although I can create delete and remove application extensions themselves via [Get/New/Remove]-AzureADApplicationExtensionProperty).
I know from watching the API calls with Fiddler that the graph calls are using bearer tokens, and I've called the graph API directly from Postman manually so I know how to use the Bearer token if I could get it. How do I get it?
To get the token simply use:
$token = [Microsoft.Open.Azure.AD.CommonLibrary.AzureSession]::AccessTokens['AccessToken']
But how could one come to this conclusion?
First look for where the module is located:
(Get-Module AzureAd).Path
C:\Program Files\WindowsPowerShell\Modules\AzureAD\2.0.1.3\Microsoft.Open.AzureAD16.Graph.PowerShell.dll
Now lets just make 2 assumptions. First that the token is stored in a static member of a static class, and second that it might not be stored in that dll, but any of the DLLs in the folder.
$fileInfo = New-Object 'IO.FileInfo' (Get-Module AzureAd).Path
$moduleFolder = $fileInfo.Directory.FullName
$assemblies = [AppDomain]::CurrentDomain.GetAssemblies() | where { $_.Location -ne $null -and $_.Location.StartsWith($moduleFolder)}
$assemblies | select -expandproperty ExportedTypes | Where { $_.IsSealed -and $_.IsAbstract } | Select Name, FullName
That last line btw is because of the weird way static types are noted in IL.
Which outputs a very small list:
Name FullName
---- --------
RestSharpExtensionMethods Microsoft.Open.Azure.AD.CommonLibrary.RestSharpExtensionMethods
AzureSession Microsoft.Open.Azure.AD.CommonLibrary.AzureSession
DictionaryExtensions Microsoft.Open.Azure.AD.CommonLibrary.DictionaryExtensions
Logger Microsoft.Open.Azure.AD.CommonLibrary.Logger
ImageUtils Microsoft.Open.Azure.AD.CommonLibrary.Utilities.ImageUtils
SecureStringExtension Microsoft.Open.Azure.AD.CommonLibrary.Extensions.SecureStringExtension
AzureEnvironmentConstants Microsoft.Open.Azure.AD.CommonLibrary.AzureEnvironment+AzureEnvironmentConstants
TypeToOdataTypeMapping Microsoft.Open.AzureAD16.Client.TypeToOdataTypeMapping
JsonConvert Newtonsoft.Json.JsonConvert
Extensions Newtonsoft.Json.Linq.Extensions
Extensions Newtonsoft.Json.Schema.Extensions
TypeToOdataTypeMapping Microsoft.Open.MSGraphV10.Client.TypeToOdataTypeMapping
AdalError Microsoft.IdentityModel.Clients.ActiveDirectory.AdalError
AuthenticationContextIntegratedAuthExtensions Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions
AdalOption Microsoft.IdentityModel.Clients.ActiveDirectory.AdalOption
MiscExtensions RestSharp.Extensions.MiscExtensions
ReflectionExtensions RestSharp.Extensions.ReflectionExtensions
ResponseExtensions RestSharp.Extensions.ResponseExtensions
ResponseStatusExtensions RestSharp.Extensions.ResponseStatusExtensions
StringExtensions RestSharp.Extensions.StringExtensions
XmlExtensions RestSharp.Extensions.XmlExtensions
RestClientExtensions RestSharp.RestClientExtensions
SimpleJson RestSharp.SimpleJson
We could pipe through Out-Gridview if the list was longer, but my attention was immediatly drawn to AzureSession. After that a little PowerShell autocomplete, and I found my way to [Microsoft.Open.Azure.AD.CommonLibrary.AzureSession]::AccessTokens['AccessToken']

Using querySelectorAll on an mshtml.HTMLDocumentClass object in PowerShell causes a crash

I'm trying to do some web-scraping via PowerShell, as I've recently discovered it is possible to do so without too much trouble.
A good starting point is to just fetch the HTML, use Get-Member, and see what I can do from there, like so:
$html = Invoke-WebRequest "https://www.google.com"
$html.ParsedHtml | Get-Member
The methods available to me for fetching specific elements appear to be the following:
getElementById()
getElementsByName()
getElementsByTagName()
For example I can get the first IMG tag in the document like so:
$html.ParsedHtml.getElementsByTagName("img")[0]
However after doing some more research in to whether I could use CSS Selectors or XPath I discovered that there are unlisted methods available, since we are just using the HTML Document object documented here:
querySelector()
querySelectorAll()
So instead of doing:
$html.ParsedHtml.getElementsByTagName("img")[0]
I can do:
$html.ParsedHtml.querySelector("img")
So I was expecting to be able to do:
$html.ParsedHtml.querySelectorAll("img")
...in order to get all of the IMG elements. All the documentation I've found and googling I've done supports this. However, in all my testing this function crashes the calling process and reports a heap corruption exception code in the Event Log (0xc0000374).
I'm using PowerShell 5 on Windows 10 x64. I've tried it in a Win10 x64 VM that is a clean build and just patched up. I've also tried it in Win7 x64 upgraded to PowerShell 5. I haven't tried it on anything prior to PowerShell 5 as all our systems here are upgraded, but I probably will once I have time to spool a new vanilla VM for testing.
Has anyone run in to this issue before? All my research so far is a dead end. Are there alternatives to querySelectorAll? I need to scrape pages that will have predictable sets of tags inside unpredictable layouts and potentially no IDs or classes assigned to the tags, so I want to be able to use selectors that allow structure/nesting/wildcards.
P.S. I've also tried using the InternetExplorer.Application COM object in PowerShell, the result is the same, except instead of PowerShell crashing Internet Explorer crashes. This was actually my original approach, here's the code:
# create browser object
$ie = New-Object -ComObject InternetExplorer.Application
# make browser visible for debugging, otherwise this isn't necessary for function
$ie.Visible = $true
# browse to page
$ie.Navigate("https://www.google.com")
# wait till browser is not busy
Do { Start-Sleep -m 100 } Until (!$ie.Busy)
# this works
$ie.document.getElementsByTagName("img")[0]
# this works as well
$ie.document.querySelector("img")
# blow it up
$ie.document.querySelectorAll("img")
# we wanna quit the process, but since we blew it up we don't really make it here
$ie.Quit()
Hope I'm not breaking any rules and this post makes sense and is relevant, thanks.
UPDATE
I tested earlier PowerShell versions. v2-v4 crash using the InternetExplorer.Application COM method. v3-4 crash using the Invoke-WebRequest method, v2 doesn't support it.
I ran into this problem, too, and posted about it on reddit. I believe the problem happens when Powershell tries to enumerate the HTML DOM NodeList object returned by querySelectorAll(). The same object is returned by childNodes() which can be enumerated by PS, so I'm guessing there's some glue code written for .ParsedHtml.childNodes but not .ParsedHtml.querySelectorAll(). The crash can be triggered by Intellisense trying to get tab-complete help for the object, too.
I found a way around it, though! Just access the native DOM methods .item() and .length directly and emit the node objects into a PowerShell array. The following code pulls the newest page of posts from /r/Powershell, gets the post list anchors via querySelectorAll() then manually enumerates them using the native DOM methods into a Powershell-native array.
$Result = Invoke-WebRequest -Uri "https://www.reddit.com/r/PowerShell/new/"
$NodeList = $Result.ParsedHtml.querySelectorAll("#siteTable div div p.title a")
$PsNodeList = #()
for ($i = 0; $i -lt $NodeList.Length; $i++) {
$PsNodeList += $NodeList.item($i)
}
$PsNodeList | ForEach-Object {
$_.InnerHtml
}
Edit .Length seems to work capitalized or lower-case. I would have expected the DOM to be case-sensitive, so either there's some things going on to help translate or I'm misunderstanding something. Also, the CSS selector is grabbing the source links (self.PowerShell mostly), but that it my CSS selector logic error, not a problem with querySelectorAll(). Note that the results of querySelectorAll() are not live, so modifying them won't modify the original DOM. And I haven't tried modifying them or using their methods yet, but clearly we can grab at the very least .InnerHtml.
Edit 2: Here is a more-generalized wrapper function:
function Get-FixedQuerySelectorAll {
param (
$HtmlWro,
$CssSelector
)
# After assignment, $NodeList will crash powershell if enumerated in any way including Intellisense-completion while coding!
$NodeList = $HtmlWro.ParsedHtml.querySelectorAll($CssSelector)
for ($i = 0; $i -lt $NodeList.length; $i++) {
Write-Output $NodeList.item($i)
}
}
$HtmlWro is an HTML Web Response Object, the output of Invoke-WebReqest. I originally tried to pass .ParsedHtml but then it would crash on assignment. Doing it this way returns the nodes in a Powershell array.
The #midnightfreddie's solution worked fine for me before, but now it throws Exception from HRESULT: 0x80020101 when calling $NodeList.item($i).
I found the following workaround:
function Invoke-QuerySelectorAll($node, [string] $selector)
{
$nodeList = $node.querySelectorAll($selector)
$nodeListType = $nodeList.GetType()
$result = #()
for ($i = 0; $i -lt $nodeList.length; $i++)
{
$result += $nodeListType.InvokeMember("item", [System.Reflection.BindingFlags]::InvokeMethod, $null, $nodeList, $i)
}
return $result
}
This one works for New-Object -ComObject InternetExplorer.Application as well.

Convert C# logic to powershell for TFS

I have a C# program which build me a TFS build definition. I want to do the same code in a powershell script. So far, I have been able code the script which will create me a new build definition in TFS. However, I have trouble setting Process section of the build definition. I need to convert the below code in C# to powershell and all attemps I have made did not work.
//Set process parameters
var process = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);
//Set BuildSettings properties
BuildSettings settings = new BuildSettings();
settings.ProjectsToBuild = new StringList("$/Templates/Main/Service/application1");
settings.PlatformConfigurations = new PlatformConfigurationList();
settings.PlatformConfigurations.Add(new PlatformConfiguration("Any CPU", "Debug"));
process.Add("BuildSettings", settings);
buildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
First I loaded the assemblies I need to work with TFS. When I want to replicate the same C# code as,
var process = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);
I did following in PowerShell
$process = New-Object Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers.
Above gave me an error saying "Constructor not found. Cannot find an appropriate constructor for type Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers"
I checked and there are no constructors for that. My question is what I am I doing wrong in writing the PowerShell script to achieve the same functionality as c# code. I am sure it's syntax error that I am doing and not aware of the correct way of doing it in PowerShell.
It would appear from your code snippet (and confirmed via MSDN) that the DeserializeProcessParameters is a static method on the WorkflowHelpers class. You would need to invoke it with the following syntax in PowerShell:
$process = [Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers]::DeserializeProcessParameters($buildDefinition.ProcessParameters)
It looks like the buildDefinition variable is declared earlier - so I just stuck a $ character on it to make it a legit PowerShell variable. Same thing with the process variable. I hope this helps!

powershell com object windows installer

I would like to leverage the following
$wi=new-object -com WindowsInstaller.Installer
If I do a $wi |gm I do not see the method I want "Products".
I would like to iterate Products and show a list of all items installed on the system.
So I thought... let me do a $wi.gettype().invokemember
Not really sure what to do $wi.gettype().invokemember("Products","InvokeMethod")
or something yields cannot find an overload...
But now I am lost. I have looked elsewhere but I don't want to create a whole XML file. I should be able to access the com objects methods.
If you are trying to get a list of installed programs in Windows, there is a native Powershell way, which is actually using WMI behind the scenes:
Get-WmiObject Win32_Product
Here's a related article from Microsoft Scripting Guys.
It appears that this approach has some issues, so better be avoided.
When you query this class, the way the provider works is that it
actually performs a Windows Installer “reconfiguration” on every MSI
package on the system as its performing the query!
I tried my best to find a solution that involves WindowsInstaller com object, but all of them point to an article that no longer exists. Here is one on stackoverflow.
An alternative solution is to give a try to psmsi on CodePlex.
Here my code-snippet for this:
cls
$msi = New-Object -ComObject WindowsInstaller.Installer
$prodList = foreach ($p in $msi.Products()) {
try {
$name = $msi.ProductInfo($p, 'ProductName')
$ver = $msi.ProductInfo($p, 'VersionString')
$guid = $p
[tuple]::Create($name, $ver, $guid)
} catch {}
}
$prodlist

Run my third-party DLL file with PowerShell

I am not sure if this is possible or not with PowerShell.
But basically I have a Windows Forms program that configures a program called EO Server. The EO Server has an API, and I make a reference to EOServerAPI.dll to make the following code run.
using EOserverAPI;
...
private void myButton_Click(object sender, EventArgs e)
{
String MDSConnString="Data Source=MSI;Initial Catalog=EOMDS;Integrated Security=True;";
//Create the connection
IEOMDSAPI myEOMDSAPI = EOMDSAPI.Create(MDSConnString);
//Get JobID
Guid myMasterJobID = myEOMDSAPI.GetJobID("myJobRocks");
}
Is it possible to interact with an API DLL file and make the same types of calls as you would in a Windows Forms application?
Yes, you can:
Add-Type -Path $customDll
$a = new-object custom.type
You call a static method like so:
[custom.type]::method()
Instead of Add-Type, you can also use reflection:
[Reflection.Assembly]::LoadFile($customDll)
(Note that even the above is calling the Reflection library and the LoadFile static method.)
Take a look at the blog post Load a Custom DLL from PowerShell. If you can interact with an object in .NET, you can probably do it in PowerShell too.
Actually the other offered solutions don't work for me, here it's an alternative that works perfectly for me:
$AssemblyPath = "C:\SomePath\SomeLIB.dll"
$bytes = [System.IO.File]::ReadAllBytes($AssemblyPath)
[System.Reflection.Assembly]::Load($bytes)
c# dll
Add-Type -Path $dllPath
(new-object namespace.class)::Main() #Where namespace=dllnamespace, class=dllclass, Main()=dllstartvoid
info. get namespace&classes
$types = Add-Type -Path $dllPath -PassThru
$types | ft fullname
$types
if it's not "executable" dll (something get/set dll) then this is the best that i know (not needed vs for sample dll creating):
https://kazunposh.wordpress.com/2012/03/19/проверка-корректного-ввода-distinguished-name-в-скри/