Get installed software product context using powershell - powershell

I can easily get all installed software products on a machine using
Get-WmiObject -Class Win32_Product
Now I'd like to also fetch the Product Context. How can I access this information for every installed product using PowerShell.
In VB I did that by using the WindowsInstaller COM-Object and then querying the information. In essence this:
Set Com = CreateObject('WindowsInstaller.Installer')
Set Products = Com.ProductsEx(vbNullString,"S-1-1-0",7)
For Each P in Products
context = P.Context
Which I dont not manage to replicate in PowerShell

I realize this question is a bit stale, but I disagree with what seems to be the prevailing notion that working with Windows Installer in PowerShell is somehow a "pain" and more complicated than working with it in VBScript (this post is just one of many).
I have found that VBScript Windows Installer code translates quite literally to PowerShell, which means there are numerous examples of VBScript Windows Installer scripts that can be adapted to PowerShell and used to learn how to work with Windows Installer in PowerShell.
For this specific question of install context, the PowerShell code is quite similar to the VB code the OP gave.
# code must be run with admin rights to use "S-1-1-0" SID
enum InstallContext {
FirstVisible = 0 # product visible to the current user
None = 0 # Invalid context for a product
UserManaged = 1 # user managed install context
UserUnmanaged = 2 # user non-managed context
Machine = 4 # per-machine context
All = 7 # All contexts. OR of all valid values
AllUserManaged = 8 # all user-managed contexts
}
$Installer = New-Object -ComObject WindowsInstaller.Installer
foreach ($P in $Installer.ProductsEx("", "S-1-1-0", 7)) {
[InstallContext]$P.Context()
}
NOTE: I used Enums (about Enum - PowerShell | Microsoft Docs) with PowerShell here since tagMSIINSTALLCONTEXT is an enum in the msi.h file.

It's a pain to use that com object in powershell. I would use vbscript instead and save the text output to a powershell variable, or find an msi powershell module. That com object doesn't have a "type library" or support "IDispatch". The Windows Powershell in Action appendix for 2nd edition goes into it, but even there it's not pretty. That vbscript code has errors.

Related

Why COM object methods are not visible/accessible in powershell?

As described here, COM objects can be created using New-Object -ComObject ProgID.
With the same steps we can create COM object with ProgID wscAPI.WSCProductList.1. But we see no methods are available whereas there are few as per docs here. Why these methods are not shown/accessible in powershell?
PS C:\>$Wsc = New-Object -ComObject wscAPI.WSCProductList.1
PS C:\>$Wsc | Get-Member
TypeName: System.__ComObject
Name MemberType Definition
---- ---------- ----------
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
ToString Method string ToString()
PS: There is a sample code to access these methods using C++ here.
OS: Microsoft Windows 10 Enterprise
Well, by design, PowerShell is not a COM Object Browser. COM objects are only loaded when instantiated and called.
PowerShell is .Net based, and .Net is part is the Operating system and loaded when the OS starts. This is the reason what you get the .Net namespaces in IntelliSense In PowerShell without extra work. Though even with that one needs to call it correctly. For example,
[System.Net.Dns]::
… or if you want if you don’t what to type all that, you can put this…
Using Namespace System.Net
at the very top of your profile / your script or the first thing you’d put in an interactive session, then you could just due
[Dns]::
About Using It allows you to indicate which namespaces are used
in the session.
COM would not be available to you in this manner. As for your pointer the C++ code. You can use external code segments/blocks/functions in PowerShell to supplement what you may be trying to do and this is what the Add-type cmdlet is used for.
Examples ---
Using CSharp (C#) code in Powershell scripts
As noted online:
from within PS run help add-type -full and look for the F# example
(example 7 on my system). Assuming you have a C++ compiler that
follows the "CodeDom" model, you can mirror the F# example.
Also, PowerShell can call unmanaged code using P/Invoke (this is a .NET concept), search the web for the use case.
So, based on even what you are showing, you are talking beyond this?
$wscAPI = New-Object -ComObject wscAPI.WSCProductList.1
$wscAPI.CreateObjRef(
... so, you want to be able to say to have IntelliSense/tab completion for when you start typing
New-Object -ComObject #[tab if on the command line or IntelliSense popup in an editor]
Yet there are modules/scripts, etc. via the Microsoft powershellgallery.com, the demonstrate how you could write your own object browser. See the resources below:
PowerShell Object Browser
This is a PowerShell application that displays objects in an explorer
type application written in PowerShell. The PowerShell Object Browser
displays the properties and child objects/collections for any
PowerShell / .Net framework object. For an explanation on how to use
this a
Download: PSObjectBrowser.ps1
There are several WMI, CIM, .Net examples/tools that show how to do
object browsing, but again, that .Net. Just search for them as well.
For Example:
PowerShell Object Browser
This is a PowerShell application that displays objects in an explorer
type application written in PowerShell. The PowerShell Object Browser
displays the properties and child objects/collections for any
PowerShell / .Net framework object. For an explanation on how to use
this a
Download: PSObjectBrowser.ps1
Weekend Scripter: The WMI Explorer Tool
Weekend Scripter: Announcing WMI Explorer 2.0
PowerShell ISE Add-on - CIM Explorer
PowerShell ISE add-on for navigating CIM(WMI) namespaces/definitions
and creating PowerShell script examples through CIM cmdlets
Free WMI Explorer, WMI Tools & Powershell GUI Builder
Coretech WMI and PowerShell Browser
· A powerful WMI tool that allows you to connect to and work with WMI
locally and remotely. With the tool, you can browse WMI namespaces,
classes, methods, properties and class instances. Besides being a WMI
browser the Coretech WMI and PowerShell Explorer allows also to export
query results and namespace methods.
These could give you ideas regarding how you might design one for COM. Yet, you'd have to know all the possible COM classes (registered or not) you'd like to instantiate. So, you could do something like this to find them ...
Get-ChildItem -Path 'HKLM:\Software\Classes' -ErrorAction SilentlyContinue |
Where-Object {$PSItem.PSChildName -match '^\w+\.\w+$' -and
(Get-ItemProperty "$($PSItem.PSPath)\CLSID" -ErrorAction SilentlyContinue)} |
Format-Table PSChildName -AutoSize
# Results
<#
Sample output from the COM lookup.
PSChildName
-----------
...
Access.Application
...
AcroPDF.PDF
...
AudioVBScript.1
...
CDO.Message
...
Cmiv2.CmiFactory
...
Excel.Application
...
InternetExplorer.Application
...
Outlook.Application
...
Paint.Picture
...
#>
One, of course, can modify the regex to look up whatever string(s) they choose. See the Regular Expression resource for detailed guidance on regex use.
RegExLib.com Regular Expression Cheat Sheet (.NET)
Regular-Expressions.Info Tutorials
So, with that block for say this effort...
New-Object -ComObject #[tab if on the command line or IntelliSense popup in an editor]
... as long as you load this at startup, and give them a populated variable name, that would be a quick and dirty way to do this. I've done similar things not only for this but for the .Net classes as well...
Function Get-AllCimClassesAsVariables
{
# Set variables for CimClasses
ForEach($Item in ((Get-CimClass -ClassName *).CimClassName))
{Set-Variable -Name $Item -Value $Item}
}
Point of note here:
I don't use this approach anymore, because it is cumbersome, has a heavy startup load because it would have to populate this list on every PowerShell session.
I have full Visual Studio on my workstation (one can use the paid-for or free version), as well, so I use it for PowerShell development stuff as well to reference and browse COM resources.
Prior to that, I used OleView.exe from the Windows / MSOffice SDKs. There was also in the Windows Resource kits that were free to download. I still have that in a cloud location for my easy access, pinned to my taskbar when I just need that vs spinning up Visual Studio or when I am in a location where I don’t have Visual Studio at hand.
To do what you are after, it would be prudent just to use OleVIew.exe as part of your PowerShell toolset to look up and use the COM info, find the ones you'd regularly use and put those into a module you regularly load via your profile or you'll need to gin up whatever thingy you need for browsing the similar to what tools are shown for WMI/CIM/.Net ones above.

How to get an environment variable in a Powershell script when it is deployed by SCCM?

I've made a script to automatically change and/or create the default Outlook signature of all the employees in my company.
Technically, it gets the environment variable username where the script is deployed, access to the staff database to get some information regarding this user, then create the 3 different files for the signature by replacing values inside linked docx templates. Quite easy and logical.
After different tests, it is working correctly when you launch the script directly on a computer, either by using Powershell ISE, directly by the CMD or in Visual Studio. But when we tried to deploy it, like it will be, by using SCCM, it can't get any environment variable.
Do any of you have an idea about how to get environment variables in a script when it is deployed by SCCM ?
Here is what I've already tried :
$Name = [Environment]::UserName
$EnvVarUserName = Get-Item Env:\USERNAME
Even stuff like this :
$proc = gwmi win32_process -Filter "Name = 'explorer.exe'"
$report = #()
ForEach ($p in $proc)
{
$temp = "" | Select User
$temp.user = ($p.GetOwner()).User
$report += $temp
}
Thanks in advance and have a nice day y'all !
[EDIT]:
I've found a way of doing this, not the best one, but it works. I get the name of the machine, check the DB where when a laptop is connected to our network it stores the user id and the machine, then get the info in the staff DB.
I will still check for Matt's idea which is pretty interesting and, in a way, more accurate.
Thank you all !
How are you calling the environmental variable? $Env:computernamehas worked for me in scripts pushed out via SCCM before.
Why don't you enumerate the "%SystemDrive%\Users" folder, exclude certain built-in accounts, and handle them all in one batch?
To use the UserName environment variable the script would have to run as the logged-in user, which also implies that all of your users have at least read access to your staff database, which, at least in our environment, would be a big no-no.

Is there a powershell replacement for Repadmin /syncall

Currently we are using the command repadmin /syncall /e [our dn] and repadmin /syncall /eP [our dn] to force replication betwen domain controllers. I am wanting to use powershell to sync the domain controllers but everything I see online indicates that I would have to simply call repadmin from within powershell, which to me seems hokey and like duct taping something instead of doing it right. Is there any PURE powershell equivelant of repadmin /syncall?
I wrote this pure powershell function/script back last year to do exactly this and it looks like maybe that's where the other posted PS snippet answer here came from (I'll take as compliment). The other post saying it is not possible is absolutely incorrect this ADSI call and my script does in fact force a full sync just like a Repadmin /syncall simply test it and you will see - I use it quite a bit. It also does debug output and proper error checking. Here's the link to the Pure Powershell script on the MSDN site:
http://bit.ly/SyncADDomain
and the github repo where I even have the pure powershell script packaged into a MSI installer for simple deployment as well:
https://github.com/CollinChaffin/SyncADDomain
If you find it helpful please mark as answer. Thanks!
AFIAK there's not a full replacement for repadmin. Sync-ADObject will let you replicate a single object, but won't let you do a full sync. Also, that cmdlet is Windows 8.1/Windows Server 2012 R2 only. I would expect more comprehensive AD replication support in Windows Server vNext.
Give this script a try:
[CmdletBinding()]
Param([switch]$AllPartitions)
$myDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain();
ForEach ($dc in $myDomain.DomainControllers) {
$dcName = $dc.Name;
$partitions = #();
if ($AllPartitions) {
$partitions += $dc.Partitions;
} else {
$partitions += ([ADSI]"").distinguishedName;
}
ForEach ($part in $partitions) {
Write-Host "$dcName - Syncing replicas from all servers for partition '$part'"
$dc.SyncReplicaFromAllServers($part, 'CrossSite')
}
}

How do I get a list of all tfs alert subscriptions via powershell (get .Net assembly via PowerShell?)

I'm trying to move my tools to powershell, can this be done in PowerShell? the bit I'm really interested in is:
IEventService es = tfs.GetService(typeof(IEventService)) as IEventService;
List<Subscription> ls = es.GetAllEventSubscriptions().ToList();
Edit: what I really want to do might be using a .NET assembly from powershell and this might then be a duplicate of Using .NET library from PowerShell
Here is a TFS API in PowerShell function that I found on a blog long ago that will get you started. I've posted it to a GitHub Gist. Basically you ensure you've loaded up the TFS assemblies into the AppDomain and then you can add any TFS Service Interfaces you want to the object and just operate on them just as you would in any c# application, etc.
https://gist.github.com/3288447
Once you have the TFS object returned from the method in the Gist above, you can operate on the loaded services like so:
#use work item service
$tfs = get-tfs $env:TFSSERVERURL -silent
$project = $tfs.wit.Projects | ?{ $_.Name -eq $projectName}
#todo - replace with text for query or file read -- this is deprecated
$query = $project.StoredQueries | ?{ $_.Name -eq 'Active Bugs' }
$queryText = $query.QueryText.Replace("#project","'$projectName'")
$results = $tfs.wit.Query($queryText)
#do something with the results...
In your request above you can just alter the get-tfs method to add your service interface to the set loaded and then operate on the .NET methods much like I do in the example above.

Is the PowerShell ConsoleShell on .NET 4.0 approved for production?

After reading several other blog posts and articles (references found below) there appear to be several ways to run PowerShell on .NET 4.0 but few are sufficient for our purposes. Due to how we deploy our software we cannot update the registry or change add an application. This leaves us with two options, create our own shell by using ConsoleShell or override PSHost. We would like to be able to use the first option, ConsoleShell, due to it's simplicity but would like to know what issues we may encounter and whether doing so is recommended.
Reference
Based on other questions I have seen that you can use the following methods to run PowerShell as .NET 4.0. None of these methods appear to be officially sanction by Microsoft but the first shown below is included as a work around in this Microsoft connect issue.
The options to run PowerShell in .NET 4.0 appear to include:
Update the app.config to include .NET 4.0 as supported
Add a registry setting to switch the version
Use ConsoleShell to host your own PowerShell Console
Implement your own PowerShell host, PSHost , as done by PoshConsole or Nuget
As far as it is not officially approved (to my knowledge), then nobody can approve it but you. If your scenario works and passes reasonable tests, approve and use it.
I use PowerShell on .NET 4.0 but with the option 4, and used to use the option 1, too (I do not like the option 2, personally). Thus, I approved it, production or not, I use it a lot and it works. I still use PowerShell on .NET 2.0 for two reasons: 1) PowerShell.exe starts faster (especially x64); 2) to be sure that part of my PowerShell development is compatible with .NET 2.
Another thought. If something does not work properly in PowerShell on .NET 2.0 (there are some issues, indeed, see Connect) then the fact "it is approved for production" itself does not help much. One has to overcome existing issues, .NET 2 or .NET 4 does not matter.
P.S. I should have mentioned that I tried the option 3 as well. I did not find use cases suitable for using it in my scenarios. But I did not find any issues in using ConsoleShell either.
P.P.S Yet another option. Make a copy of PowerShell.exe, rename it into MyConsoleShell.exe, use it together with MyConsoleShell.exe.config configured for .NET 4. As far as you are going to use a separate application anyway, then why not to consider this?
I'm a bit of a powershell N00b, but I threw this together as a way of forcing an arbitrary script to use .NET 4.0 in my script:
# Place this at the top of your script, and it will run as a .NET 4 ps script.
# #############################################################################
if ($PSVersionTable.CLRVersion.Major -lt 4) { try {
$cfgPath = $Env:TEMP | Join-Path -ChildPath ([Guid]::NewGuid())
mkdir $cfgPath | Out-Null
"<configuration><startup useLegacyV2RuntimeActivationPolicy='true'><supportedRuntime version='v4.0'/></startup></configuration>" | Set-Content -Path $cfgPath\powershell.exe.activation_config -Encoding UTF8
$darkMagic = 'COMPLUS_ApplicationMigrationRuntimeActivationConfigPath'
$old = [Environment]::GetEnvironmentVariable($darkMagic)
[Environment]::SetEnvironmentVariable($darkMagic, $cfgPath)
& powershell.exe $MyInvocation.MyCommand.Definition $args
} finally {
[Environment]::SetEnvironmentVariable($darkMagic, $old)
$cfgPath | Remove-Item -Recurse
return
}}
# ##############################################################################
# My script starts here:
echo "Your arguments are: $args "
echo "The CLR Major Version is : $($PSVersionTable.CLRVersion.Major)"
It places a check in the beginning of the script, and if it's not .NET 4.0 it creates a configuration file, sets an environment variable and re-runs the powershell script so that it runs under .NET 4.0.
it does incur a bit of a startup time penalty of about a second or so on my pc, but at least it works :)