What do Get-WMIObject \ Get-CimInstance actually do? - powershell

I have developing a new WMI instance provider and I am having a bit of trouble. I am able to register my provider successfully using regsvr32.exe. The regsvr32 application calls my implementation of DllRegisterServer and creates the following registry keys and values:
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{00000001-0000-0000-0000-00000000000F} : (default) = "WMI Provider"
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{00000001-0000-0000-0000-00000000000F}\InprocServer32 : (default) = "C:\MyWmiProvider.dll"
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{00000001-0000-0000-0000-00000000000F}\InprocServer32 : ThreadingModel = Neutral
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{00000001-0000-0000-0000-00000000000F}\Version : (default) = 1.0.0
(Where {00000001-0000-0000-0000-00000000000F} is just a test Class ID (CLSID))
I am also able to successfully add my WMI class definitions defined in my Managed Object Format (MOF) file by using mofcomp.exe. I am able to verify that my definitions are present in the WMI repository by running the following command:
Get-CimClass -Namespace "root/MyNamespace" | Where-Object CimClassName -like "MyClass_*"
Here is an example of what my MOF file looks like:
#pragma namespace("\\\\.\\root\\MyNamespace")
#pragma autorecover
instance of __Win32Provider as $P
{
Name = "MyWmiProvider";
ClsId = "{00000001-0000-0000-0000-00000000000F}";
};
instance of __InstanceProviderRegistration
{
Provider = $P;
SupportsGet = FALSE;
SupportsPut = FALSE;
SupportsDelete = FALSE;
SupportsEnumeration = TRUE;
};
[dynamic, provider("MyWmiProvider")]
class MyClass_ExampleName
{
[key]
uint14 Id;
[PropertyContext("Name")]
String Name;
};
Now, if I run the following:
Get-CimInstance -Namespace "root/MyNamespace" -Class "MyClass_ExampleName"
This produces the following error in PowerShell:
Get-CimInstance : Provider load failure
At line:1 char:1
+ Get-CimInstance -Namespace "root/MyNamespace" -Class "MyClass_ExampleName"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (root/Surface:Device_Status:String) [Get-CimInstance], CimException
+ FullyQualifiedErrorId : HRESULT 0x80041013,Microsoft.Management.Infrastructure.CimCmdlets.GetCimInstanceCommand
Likewise, there are three (3) Event Viewer logs that are generated when this command is executed:
MyWmiProvider provider started with result code 0x80041013. HostProcess = wmiprvse.exe; ProcessID = 2144; ProviderPath = C:\MyWmiProvider.dll
Id = {FB6B3CF7-293E-0002-9316-73FB3E29D601}; ClientMachine = RTR-USERNAME; User = MYDOMAIN\username; ClientProcessId = 19416; Component = Unknown; Operation = Start IWbemServices::CreateInstanceEnum - root\MyNamespace : MyClass_ExampleName; ResultCode = 0x80041013; PossibleCause = Unknown
MyWmiProvider provider started with result code 0x80041013. HostProcess = wmiprvse.exe; ProcessID = 24636; ProviderPath = C:\MyWmiProvider.dll
(It is shown that WMI did find the DLL correctly)
I get similar results if I try calling Get-WMIObject, except the second Event Viewer log says the "Operation" was "Start IWbemServices::ExecQuery - root\MyNamespace : MyClass_ExampleName".
What exactly are Get-WMIObject and Get-CimInstance doing in the background?
I have looked up the source for Get-WMIObject [here] and despite the simple 6 lines, looking up the respect classes and function calls does not yield detailed specifics. My DLL interface only includes four (4) exported functions: DllGetClassObject(), DllCanUnloadNow(), DllRegisterServer(), and DllUnregisterServer(). I thought both Get-WMIObject and Get-CimInstance first made a call to DllGetClassObject() in order to get the WMI class factory, however if I place a function call to save a string to a temporary file within DllGetClassObject(), I notice when calling these PowerShell commands that no temporary file is created.
I got even more specific by creating a new project following this answer so that I could be able to call the following:
DEFINE_GUID(InstanceProviderClassID, 0x00000001, 0x00000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F);
IWbemServices * pLoc = NULL;
CoCreateInstance(InstanceProviderClassID, NULL, CLSCTX_INPROC_SERVER, IID_IWbemServices, (LPVOID *)&pLoc);
The call to CoCreateInstance() in this case was successful. I even noticed the temporary log file was created, indicating that DllGetClassObject() was in fact called!
Even if I take a working instance provider that I have and put this same printing statement (or similarly a call to create a registry key/value) in its DllGetClassObject() function, nothing is ever saved to indicate that this function is called when calling these PowerShell commands.
1. What am I missing here?
2. Why is the DllGetClassObject() function never called when executing both Get-WMIObject and Get-CimInstance?
3. Why am I able to execute CoCreateInstance() successfully, demonstrating my provider is coded correctly, but get a "provider load failure" when executing one of the PowerShell commands?
(A side note: In order to make things easy, I have labeled all service functions with WBEM_E_NOT_SUPPORTED. When this is done in the working instance provider, I still do not see "provider load failure," but rather "not supported.")

Get-CimInstance and Get-WmiObject both use .NET APIs to communicate with WMI. In the first case, it uses https://learn.microsoft.com/en-us/dotnet/api/microsoft.management.infrastructure and in the latter case uses https://learn.microsoft.com/en-us/dotnet/api/system.management. The main difference between these two is that the CIM APIs are cross-platform compatible, while the WMI APIs have Windows extensions to CIM that only work on Windows.
Whether you decide to use the PowerShell cmdlets, WMIC, wbemtest, or something else, they ultimately call the WMI COM API https://learn.microsoft.com/en-us/windows/win32/wmisdk/com-api-for-wmi to talk to WMI to talk to WMI Providers.
Did you follow https://learn.microsoft.com/en-us/windows/win32/wmisdk/writing-an-instance-provider? It doesn't seem like you may have implemented IWbemServices in your COM server?

Related

ExtendedPropertyDefinition declaration for EmailMessage in Powershell throws exception

since I got to know that ExtendedProperties have its limit for a specific mailbox in the EWS cloud I am trying to switch up my code to have only one ExtendedProperty and just change its value each time I am assigning the property to an e-mail message I am sending to then find it and work on the e-mail message object later on in the program.
I am having a hard time setting this up correctly even though I am following the docs, but it just seems to not work out for me.
This is the code part that throws an Exception: "Multiple ambigious overloads found for "ExtendedPropertyDefinition" and the argument count "3" :
# email declaration exposing the $email object
.
.
.
# property declaration and setting the value
# since I want to have only one extended property, this is actually a valid GUID string that I then # convert to a Guid type
$GUIDproperty = "00000000-0000-0000-0000-000000000000"
$propertyGUID = [Guid]$GUIDproperty
# since I want to have a unique value each time set to the existing extended property
$propertyValue = [guid]::NewGuid().ToString()
$propertyName = "Id"
$ExtendedProperty = [Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition]::new($propertyGUID, $propertyName, $propertyType)
# well I dont even reach this part, but just for the big picture
$email.SetExtendedProperty($ExtendedProperty, $propertyValue)
The docs I have followed for that are the following:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.extendedpropertydefinition.-ctor?view=exchange-ews-api#microsoft-exchange-webservices-data-extendedpropertydefinition-ctor(microsoft-exchange-webservices-data-defaultextendedpropertyset-system-string-microsoft-exchange-webservices-data-mapipropertytype)
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.folder.setextendedproperty?redirectedfrom=MSDN&view=exchange-ews-api#Microsoft_Exchange_WebServices_Data_Folder_SetExtendedProperty_Microsoft_Exchange_WebServices_Data_ExtendedPropertyDefinition_System_Object_
https://learn.microsoft.com/en-us/dotnet/api/system.guid?view=net-7.0
The following works okay for me
$propertyType = [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String
$GUIDproperty = "82e3d64f-e26d-4321-8fc3-c31aa790197c"
$propertyGUID = [Guid]$GUIDproperty
$propertyValue = [guid]::NewGuid().ToString()
$propertyName = "MyPropId"
$ExtendedProperty = [Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition]::new($propertyGUID, $propertyName, $propertyType)
return $ExtendedProperty
You don't specify what you using in the $propertyType so that maybe it, could also be to do with the versions you using. What version of PowerShell and the EWS Managed API are you trying ?

How can I replicate New-SmbGlobalMapping in C# code?

I am writing a service which controls docker containers. I want to have the mounted volume as an Azure share, and thus need to use the SMB Global Mapping. If I use the usual WNetAddConnection2A then I can mount the share just fine in my code, but the containers cannot see it as it is not "global". I can't find source for the PowerShell New-SmbGlobalMapping command (is there a way to see it?) and I can't find a suitable API to call. I hope someone knows the magic incantation I can put in my .NET code.
I can't find source for the PowerShell New-SmbGlobalMapping command
(is there a way to see it?) and I can't find a suitable API to call. I
hope someone knows the magic incantation I can put in my .NET code.
PowerShell uses WMI
In your case, it calls
Create method of the MSFT_SmbMapping class (MSFT_SmbGlobalMapping exactly)
You can use WMI Code Creator to generate/test C# code
EDIT : Test with PowerShell.Create
Test as Admin ("requireAdministrator" in manifest) on Windows 10
Test code (C#, VS 2015) =>
// PowerShell calls CredUIPromptForCredentialsW to display the User/Password dialog (you can call it with P/Invoke if needed)
string sUser = "user#provider.com";
string sPassword = "myPassword";
System.Net.NetworkCredential networkCredential = new System.Net.NetworkCredential(sUser, sPassword, null);
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (var c in networkCredential.Password)
securePassword.AppendChar(c);
// Add reference to :
// C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll
// Add :
// using System.Management.Automation;
PSCredential psCredential = new PSCredential(networkCredential.UserName, securePassword);
// Error handling must be improved : if I pass an invalid syntax for "RemotePath" or not launched as Admin,
// nothing happens (no error, no result) (on Windows 10)
string sLocalPath = "Q:";
string sRemotePath = "\\\\DESKTOP-EOPIFM5\\Windows 7";
using (var ps = PowerShell.Create())
{
ps.AddCommand("New-SmbGlobalMapping");
ps.AddParameter("LocalPath", sLocalPath);
ps.AddParameter("RemotePath", sRemotePath);
ps.AddParameter("Credential", psCredential);
//ps.AddParameter("RequireIntegrity", false);
//ps.AddParameter("RequirePrivacy", false);
try
{
System.Collections.ObjectModel.Collection<PSObject> collectionResults = ps.Invoke();
foreach (PSObject psObl in collectionResults)
{
Console.WriteLine("Status : {0}", psObl.Members["Status"].Value.ToString());
Console.WriteLine("Local Path : {0}", psObl.Members["LocalPath"].Value.ToString());
Console.WriteLine("Remote Path : {0}\n", psObl.Members["RemotePath"].Value.ToString());
}
}
catch (ParameterBindingException pbe)
{
System.Console.WriteLine("\rNew-SmbGlobalMapping error : {0}: {1}",
pbe.GetType().FullName, pbe.Message);
}
}
// To get and remove the test mapping in PowerShell :
// Get-SmbGlobalMapping
// Remove-SmbGlobalMapping -RemotePath "\\DESKTOP-EOPIFM5\Windows 7" -Force

Get the "plain" (end-user readable) name of UWP apps installed on a system

I'm using the following PowerShell script to retrieve and save to a text file the list of UWP apps on a system. It gets the ID, name (system name) and packagefamilyname.
In addition to the name, I'm looking for a way to retrieve the plain name of the app: for example, "OneNote" instead of "Microsoft.Office.OneNote". Ideally, this name would also be localized: for example, "Calculatrice" (on a French system) instead of "Microsoft.WindowsCalculator".
I found this list of info retrieved by Get-AppxPackage but nothing like an end-user readable name... I'm not very familiar this area of expertise. Any help would be appreciated.
$installedapps = get-AppxPackage
$ids = $null
foreach ($app in $installedapps)
{
try
{
$ids = (Get-AppxPackageManifest $app -erroraction Stop).package.applications.application.id
}
catch
{
Write-Output "No Id's found for $($app.name)"
}
foreach ($id in $ids)
{
$line = $app.Name + "`t" + $app.packagefamilyname + "!" + $id
echo $line
$line >> 'c:\temp\output.txt'
}
}
write-host "Press any key to continue..."
[void][System.Console]::ReadKey($true)
[Completed updated]
You can do this pretty easily in C#. You have to reference the correct WinMDs from the Windows SDK (the actual directories will change depending on SDK version):
C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd
C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Foundation.UniversalApiContract\6.0.0.0\Windows.Foundation.UniversalApiContract.winmd
If you can't build a stand-alone EXE and just want pure PowerShell, you might be able to reference the WinMDs %systemroot%\system32\winmetadata. The code is pretty simple (I avoided await since I don't know if PowerShell has that):
// using Windows.Management.Deployment;
static void Main(string[] args)
{
GetList();
}
static void GetList()
{
var pm = new PackageManager();
var packages = pm.FindPackagesForUser("");
foreach (var package in packages)
{
var asyncResult = package.GetAppListEntriesAsync();
while (asyncResult.Status != Windows.Foundation.AsyncStatus.Completed)
{
Thread.Sleep(10);
}
foreach (var app in asyncResult.GetResults())
{
Console.WriteLine(app.DisplayInfo.DisplayName);
}
}
}
I spent some time looking for this today, and finally came up with a solution that doesn't involve a page of code, or digging around in files/registry/etc. Put the below two lines in a script or function, and it will return PoSh-friendly output which you can then pipe into ForEach-Object, Where-Object, Sort-Object, Export-CSV, etc.
$PkgMgr = [Windows.Management.Deployment.PackageManager,Windows.Web,ContentType=WindowsRuntime]::new()
$PkgMgr.FindPackages() | Select-Object DisplayName -ExpandProperty Id
The .FindPackages() method also has an overload which takes a Family Name, but the docs lead me to believe it can only accept exact names, not wildcard matches. So unless you know exactly what you are looking for, I am guessing it is best to retrieve the list of all packages, and then do your own searches on that list.
The docs do say that this will return packages for all users, and that it requires admin/elevated rights to run.

Set Execution Policy Powershell in Azure web application

I need to execute some powershell code from a web api which I have deployed in an Azure App Service. I couldn't achieve to se 'Set-ExecutionPolicy' because I need to set it to unrestricted but I get an error
File D:\home\powershell\teams_v2.psm1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170
.
I have the following code
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
var script = String.Format(#"Import-Module 'D:\home\powershell\teams_v2.psm1'
connect-teamsservice -user admin#contoso.onmicrosoft.com -tenant contoso.onmicrosoft.com
new-Team -displayname '{0}' -description '{1}' -smtpaddress '{2}' -alias '{3}' -type 'private'",
group.Name, group.Description, String.Format("{0}#contoso.onmicrosoft.com", group.MailNickName), "team");
RunspaceInvoke scriptInvoker = new RunspaceInvoke();
// set powershell execution policy to unrestricted
//scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(script);
// add an extra command to transform the script
// output objects into nicely formatted strings
// remove this line to get the actual objects
// that the script returns. For example, the script
// "Get-Process" returns a collection
// of System.Diagnostics.Process instances.
pipeline.Commands.Add("Out-String");
// execute the script
Collection <PSObject> results = pipeline.Invoke();
// close the runspace
runspace.Close();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
}
How can I achieve this to correctly load the ps module and use its functions.

How to define further commonparameters for powershell functions defined within a bespoke module?

I'm writing a powershell module to interact with AWS. Most of the functions need to take parameters that are the credentials to use - awsAccessKeyId, awsSecretKey and credentialsFile.
It's getting dull copy/pasting those parameters to each function in the module.
Is there a way to declare that these are CommonParameters for the set of functions that the module exports?
Also, is there a way to extract the common (ie, duplicated) parameter-set handling switch-statement so it can be called by all the functions that need it?
Here's an example function:
function New-S3Client {
[CmdletBinding(DefaultParametersetName="credentialsFile")]
param
(
[parameter(Mandatory=$true, ParameterSetName="specifyKey")] [string]$accessKey,
[parameter(Mandatory=$true, ParameterSetName="specifyKey")] [string]$secretKey,
[parameter(ParameterSetName="credentialsFile")] [string]$credentialsFile = "$env:USERPROFILE\.aws\credentials"
)
switch($PsCmdlet.ParameterSetName)
{
"specifyKey" {
$env:awsAccessKeyId = $accessKey
$env:awsSecretKey = $secretKey
break
}
"credentialsFile" {
$env:awsAccessKeyId = Read-ValueForKeyFromFile -from $credentialsFile -field AWSAccessKeyId
$env:awsSecretKey = Read-ValueForKeyFromFile -from $credentialsFile -field AWSSecretKey
break
}
}
$config = New-Object Amazon.S3.AmazonS3Config
$config.WithServiceURL("https://s3.eu-west-1.amazonaws.com")
$client = New-Object Amazon.S3.AmazonS3Client($env:awsAccessKeyId, $env:awsSecretKey, $config)
return $client
}
I would like to extract parameters 2-4 inclusive to CommonParameters, and then the switch block to some common function.
Most modules I've come across use a Connect-SomeService function and then handle the connections/credentials within the module state. Maybe with some parts of it exposed in the callers session (array variable keeping track of connections).
Like VMware PowerCLI...
Connect-VIServer
Get-VM
This way you only request credentials in a Connect-S3Service function. That command saves the connection in a variable that all your other functions use by default. However doing it this way mean you'll still want to have a $S3Service Common Parameter in all your other functions (incase you want to send jobs only to a specific connection), but atleast thats two parameters fewer.