Puppet fact, list software Windows - windows

I'm new to puppet and ruby, and just tried to write custom fact but ... Having the following issue
Facter.add("vsphere_installed") do
confine :operatingsystem => :windows
setcode do
if Facter::Util::Resolution.exec('c:\windows\system32\WindowsPowerShell\v1.0\powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command "Get-WmiObject -Class Win32_Product | Select-Object -DisplayName | ? {$_.DisplayName -Match "vsphere"}"') = true
result = "vSphere installed"
else
result = "false"
end
end
end
I don't know how exactly to do this, I want to list installed programs and search for one and if true(find) to return that it's installed.
This example so far returns only false ....

Puppet is about Desired State - not Procedural
It feels like you are treating Puppet as procedural at the moment and Puppet is more about the desired state. You determine what is installed, you shouldn't necessarily ask.
So on certain server roles you would say the end state is that you need vSphere and also other software.
You get to make those decisions, you shouldn't use Puppet to discover the state, but to tell it the state and let it do what it does best.
Discovery is something you can do out of band with the tool exploring a machine, try puppet resource package and you will see what I mean.
Custom Facts
But to answer your question, you should probably use a custom executable fact and just use PowerShell directly, because the command string still needs to be escaped in the double quotes (and may also need to be escaped in the way that you used apostrophe then double quotes) - the docs also point to using Facter::Core::Execution.exec and not Facter::Util::Resolution.exec.
Use Custom Executable Facts instead.
Also don't use Win32_Product - Win32_Product class can trigger Windows Installer to do a repair on all MSI installed software as a consistency check. It can really cause a machine to do a lot of unnecessary work - its just not a good idea to use it. I'd suggest querying the uninstaller registry keys directly instead.

Related

What can I do about "WMIC is deprecated"?

I've been relying on these two commands:
wmic memorychip get capacity // Outputs how much RAM there is (in a convoluted manner).
wmic diskdrive get Status,Model // Checks whether the HDDs/SSDs on the system are (supposedly) still "OK" and working.
Today, I casually typed "wmic" to see if I could get JSON output to the above commands. The first thing it printed, in red text, was this:
WMIC is deprecated.
I was pretty shocked by this. It's deprecated? Alright... Then I definitely should not be relying on it. What are the "modern alternatives" for those two commands, then? Do they even exist? Why do they just tell us that it's "deprecated" with zero further information?
As mentioned in comments, WMIC is utility that acts as interface to communication with WMI. It's not WMI itself that is being deprecated, but "just" the interface. Since Microsoft is pushing PowerShell, I believe official successor wmic would be PowerShell commandlet Get-WmiObject. How to use this can be found on Microsoft documentation: LINK
[UPDATED] As correctly pointed out within comment, commandlet Get-WmiObject may eventually sunset one day as well and thus its use may not be encouraged to have scripts future proof. Best method to stick with would be Get-CimInstance, which has pretty much the same syntax as Get-WmiObject. See Microsoft documentation: LINK
For your particular case PowerShell alternative would be the following:
wmic memorychip get capacity
Get-CimInstance -ClassName Win32_PhysicalMemory | Select-Object capacity
wmic diskdrive get Status,Model
Get-CimInstance -ClassName Win32_diskdrive | Select-Object status, model
Commands in wmic are usually derived from WMI class names, but it's not really a rule of thumb. With PowerShell you are accessing WMI by its real class name instead, so you may need to seek for other classes if needed.
As mentioned in comment by Bacon Bits. WMIC aliases to real WMI classes can be obtained by command:
wmic.exe alias list brief
Undisputed advantage to PowerShell over wmic is that output is an object and you can easily continue working with the output, while wmic returns a string only that you eventually need to parse for example if used inside scripts and that brings another benefit of e.g. output formatting - you can easily reformat any output for example to as you mentioned JSON, just pass your command through another pipe into commandlet ConvertTo-Json and you will have your expected output.
Example:
Get-CimInstance -ClassName Win32_diskdrive | select status, model | ConvertTo-JSON
Output:
{
"status": "OK",
"model": "SAMSUNG MZNTY256HDHP-000L7"
}
Hope this helps
[UPDATE 2.2.2022]
Since this is the first link on Google for this topic that pops out, here's small update:
Microsoft officially informed about wmic being deprecated in their WMIC documentation
Microsoft page quotes:
The WMI command-line (WMIC) utility is deprecated as of Windows 10, version 21H1, and as of the 21H1 semi-annual channel release of Windows Server. This utility is superseded by Windows PowerShell for WMI. This deprecation applies only to the WMI command-line (WMIC) utility; Windows Management Instrumentation (WMI) itself is not affected.
For the sake of future proof scripts, I would still stick with industrial CIM standard using PowerShell

Not Recognizing Script Name as cmdlet, function, etc; nor can positional perameter be found on simple script

I am trying to do my first script. To simply get PowerShell to pull up a script typed up in notepad and saved as a .ps1 file titled "test" (have also tried Script, but know names have nothing to do with it):
Write-Host "Hello, World!"
In PowerShell I am typing
& "C:\Scripts\test.ps1"
As well as
./test.ps1
And am only met with this:
./test.ps1.txt : The term './test.ps1.txt' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ ./test.ps1.txt
+ ~~~~~~~~~~~~~~
+ CategoryInfo: ObjectNotFound: (./test.ps1.txt:String) [], CommandNotFoundException
Have tried renaming the file within PowerShell with
PS C:\Scripts> Rename-Item test.ps1.txt test.ps1
I have switched between RemoteSigned and Unrestricted, I have tried a code including executionpolicy bypass (I do apologize, I closed my window without writing that one down). As far as I know everything is up to date and I am running Windows 10, Windows PowerShell, and regular Windows Notepad.
First, I'd HIGHLY recommend using the Windows PowerShell ISE for writing scripts. It's free, and provides a pretty decent console/editor experience, given that it's free (there are allegedly better ones out there, but this has always done just fine for me). I use Visual Studio for other stuff, and while it is an EXPONENTIALLY better product (and should be), the PowerShell ISE is pretty feature-rich.
Next, if you're just getting started, you should check out Don Jone's "Learn PowerShell 3.0 in a Month of Lunches" book. It's two versions behind the most current, however, all of the information is still relevant, and once you've finished the book, you'll be able to seek help for anything else pretty easily on your own. It covers all the basics, and is a very good first step to learning the language.
Now, to answer your question: PowerShell scripts commonly have the .ps1 file extension. Other extensions are generally used for modules (.psm1) or other helper content that Windows PowerShell leverages. For most things, you'll stick to .ps1, and when you've reached a point where you start needing the other extensions, I suspect you will have no problems identifying which ones you need.
There are two ways generally call a PowerShell script. The first is from a normal command prompt, and telling PowerShell to execute your script. This is shown below:
powershell.exe -File MyScript.ps1
There are some additional parameters that I'd recommend you use, but usage is dependent on your requirements. Here's what I usually tag on mine:
powershell.exe -NoProfile -ExecutionPolicy RemoteSigned -File MyScript.ps1
This will tell the PowerShell process to ignore any PowerShell profiles you have set up, which is ideal if you have a bunch of stuff in your profile script that does things like read console input (for your current situation, I'm going to assume you don't, but you may in the future). The other is that ExecutionPolicy one: RemoteSigned will tell PowerShell to basically ignore anything that's been downloaded from the interwebs, but allow anything originating inside your network to run free. Probably not the best practice, but this isn't a TERRIBLE policy if you can trust that your script repository is secured. If not, then go for something tighter than this (you can read up on execution policies by typing "Get-Help about_Execution_Policies" in the PowerShell prompt, or by visiting the TechNet page about them -- the content should be similar if not identical).
The second way is from inside of a Windows PowerShell script. It's actually much easier to do. Note that you must set your execution policy to something that will allow scripts to run, but thereafter, you're smooth sailing.
. .\MyScript.ps1
This is called "dot-sourcing" your script. The advantage of doing this from within Windows PowerShell is that if you've got something like a script full of functions, they get added to the current scope (Get-Help about_Scopes), which means they're now available in your current session. A good example would be defining a function called "Test-DomainConnection" in a script you distribute with your main script: You'd dot-source the script that is distributed with the main one (this is done usually when you separate your "standard" PowerShell functions from your main script), and then use the functions in the main script. There are pros and cons to this approach, but it seems to be generally recommended (there may be some community extensions out there that remove the need to manage this manually).
For additional information, you can call Get-Help about_Scripts from inside Windows PowerShell. Because you're using Windows 10, you may need to run Update-Help from an administrative PowerShell window before the help content is available on your local system.
If you have any more questions, feel free to message me :) I've been doing PowerShell for a while and may be able to help out.
Powershell processes in order (top-down) so the function definition needs to be before the function call:

Grep and tail combination for Windows equivalent

I've tried a few different solutions but haven't had any that work. I am not use to batch scripting, so this has been quite trivial for me.
Right now, I have a script in Linux that handles the execution of services in synchronous order. They depend on one another and require the other one to be completely started before they can be executed.
I am using the following line to deal with this:
grep -qi 'Service has started.\|error' <(tail -f "/opt/app/log/daemon.log")
Works great. However, this also needs to work in Windows. I've looked into using the GNU utils but I haven't really looked into their licensing, which could pose a problem. Plus, I would like to do this natively in the Windows CL.
Cheers,
Chris
P.S.
I am looking for a platform INDEPENDENT solution. Cygwin is not an answer.
You should use native Windows "dependency" of services upon one another. Use regedit.exe or sc.exe config to introduce dependencies. This way, you can leave the service startup as automatic and they will only start executing once all services this one is dependent upon has reported their condition as "started".
If you're wanting to do this natively in Windows, 'the Windows way', you'll want to be using the *-Service cmdlets (e.g. Get-Service, Stop-Service, Start-Service and Restart-Service) in PowerShell.
I'll admit, I'm not a linux user, but I assume that you checking the status of a service after starting it?
On Windows, in a PowerShell script, you'd want to do something like the following (I'm specifically picking on the Volume Shadow Copy service, because reasons):
Start-Service -Name VSS
While ((Get-Service -Name VSS).Status -ne 'Running') {
Start-Sleep -Seconds 2
}
# Start next service or continue with script here.
Apologies if this is well off base from what you're looking for. I'm just approaching this question as a Windows admin from the perspective of how I would achieve the goal.

WMI "installed" query different from add/remove programs list?

Trying to use WMI to obtain a list of installed programs for Windows XP. Using wmic, I tried:
wmic /output:c:\ProgramList.txt product get name,version
and I get a listing of many of the installed programs, but after scrubbing this list against what "Add/Remove Programs" displays, I see many more programs listed in the GUI of Add/Remove Programs than with the WMI query. Is there another WMI query I need to use to get the rest of the programs installed? Or is there some other place I need to look for the rest?
Also, there are two installed programs that are listed in the WMI query that aren't in Add/Remove programs. Any idea why?
I believe your syntax is using the Win32_Product Class in WMI. One cause is that this class only displays products installed using Windows Installer (See Here). The Uninstall Registry Key is your best bet.
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
UPDATE FOR COMMENTS:
The Uninstall Registry Key is the standard place to list what is installed and what isn't installed. It is the location that the Add/Remove Programs list will use to populate the list of applications. I'm sure that there are applications that don't list themselves in this location. In that case you'd have to resort to another cruder method such as searching the Program Files directory or looking in the Start Menu Programs List. Both of those ways are definitely not ideal.
In my opinion, looking at the registry key is the best method.
All that Add/Remove Programs is really doing is reading this Registry key:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
Besides the most commonly known registry key for installed programs:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
wmic command and the add/remove programs also query another registry key:
HKEY_CLASSES_ROOT\Installer\Products
Software name shown in the list is read from the Value of a Data entry within this key called: ProductName
Removing the registry key for a certain product from both of the above locations will keep it from showing in the add/remove programs list. This is not a method to uninstall programs, it will just remove the entry from what's known to windows as installed software.
Since, by using this method you would lose the chance of using the Remove button from the add/remove list to cleanly remove the software from your system; it's recommended to export registry keys to a file before you delete them. In future, if you decided to bring that item back to the list, you would simply run the registry file you stored.
I have been using Inno Setup for an installer. I'm using 64-bit Windows 7 only. I'm finding that registry entries are being written to
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
I haven't yet figured out how to get this list to be reported by WMI (although the program is listed as installed in Programs and Features). If I figure it out, I'll try to remember to report back here.
UPDATE:
Entries for 32-bit programs installed on a 64-bit machine go in that registry location. There's more written here:
http://mdb-blog.blogspot.com/2010/09/c-check-if-programapplication-is.html
See my comment that describes 32-bit vs 64-bit behavior in that same post here:
http://mdb-blog.blogspot.com/2010/09/c-check-if-programapplication-is.html?showComment=1300402090679#c861009270784046894
Unfortunately, there doesn't seem to be a way to get WMI to list all programs from the add/remove programs list (aka Programs and Features in Windows 7, not sure about Vista). My current code has dropped WMI in favor of using the registry. The code itself to interrogate the registry is even easier than using WMI. Sample code is in the above link.
Not the best, but whether it is practical method:
Use HijackThis. (Old version: HijackThis)
Run hijack this, click the "Open the Misc Tools section" button
click "Open Uninstall Manager"
click save list (*.txt), yes to the prompts, notepad will open with your add/remove programs list.
Source
You can get it in one line with powershell and batch file :
#echo off
Powershell /command "Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-List"
Pause
Installed products consist of installed software elements and features so it's worth checking wmic alias's for PRODUCT as well as checking SOFTWAREELEMENT and SOFTWAREFEATURE:
wmic product get name,version
wmic softwareelement get name,version
wmic softwarefeature get name,version
You can use the script from http://technet.microsoft.com/en-us/library/ee692772.aspx#EBAA to access the registry and list applications using WMI.
Hope this helps somebody: I've been using the registry-based enumeration in my scripts (as suggested by some of the answers above), but have found that it does not properly enumerate 64-bit software when run on Windows 10 x64 via SCCM (which uses a 32-bit client). Found something like this to be the most straightforward solution in my particular case:
Function Get-Programs($Bits) {
$Result = #()
$Output = (reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall /reg:$Bits /s)
Foreach ($Line in $Output) {
If ($Line -match '^\s+DisplayName\s+REG_SZ\s+(.+?)$') {
$Result += New-Object PSObject -Property #{
DisplayName = $matches[1];
Bits = "$($Bits)-bit";
}
}
}
$Result
}
$Software = Get-Programs 32
$Software += Get-Programs 64
Realize this is a little too Perl-ish in a bad way, but all other alternatives I've seen involved insanity with wrapper scripts and similar clever-clever solutions, and this seems a little more human.
P.S. Trying really hard to refrain from dumping a ton of salt on Microsoft here for making an absolutely trivial thing next to impossible. I.e., enumerating all MS Office versions in use on a network is a task to make a grown man weep.
With time having moved on quite a bit since this question was asked...
There's a WMI class available these days for the Uninstall entries in the registry. This is much quicker to reference than Win32_Product, which I think also runs verification on the list and can take a while to enumerate. The below Powershell code (possibly requires Powershell 3 or later) will list all entries (The Out-Gridview part is just for a pretty display).
Get-CimInstance Win32Reg_AddRemovePrograms | Out-gridview
Add/Remove Programs also has to look into this registry key to find installations for the current user:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall
Applications like Google Chrome, Dropbox, or shortcuts installed through JavaWS (web start) JNLPs can be found only here.
In order to build a more-or-less reliable list of applications that appear in the "Programs and Feautres" in the Control Panel, you have to consider that not all applications were installed using MSI. WMI only provides the ones installed with MSI.
Here is a short summary of what I've found out:
MSI applications always have a Product Code (GUID) subkey under HKLM\...\Uninstall and/or under HKLM\...\Installer\UserData\S-1-5-18\Products. In addition, they may have a key that looks like HKLM\...\Uninstall\NotAGuid.
Non-MSI applications do not have a product code, and therefore have keys like HKLM\...\Uninstall\NotAGuid or HKCU\...\Uninstall\NotAGuid.
I adapted the MS-Technet VBScript for my needs. It dumps Wow6432Node as well as standard entries into "programms.txt"
Use it at your own risk, no warranty!
Save as dump.vbs
From command line type: wscript dump.vbs
Const HKLM = &H80000002
Set objReg = GetObject("winmgmts://" & "." & "/root/default:StdRegProv")
Set objFSO = CreateObject("Scripting.FileSystemObject")
outFile="programms.txt"
Set objFile = objFSO.CreateTextFile(outFile,True)
writeList "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\", objReg, objFile
writeList "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\", objReg, objFile
objFile.Close
Function writeList(strBaseKey, objReg, objFile)
objReg.EnumKey HKLM, strBaseKey, arrSubKeys
For Each strSubKey In arrSubKeys
intRet = objReg.GetStringValue(HKLM, strBaseKey & strSubKey, "DisplayName", strValue)
If intRet <> 0 Then
intRet = objReg.GetStringValue(HKLM, strBaseKey & strSubKey, "QuietDisplayName", strValue)
End If
objReg.GetStringValue HKLM, strBaseKey & strSubKey, "DisplayVersion", version
objReg.GetStringValue HKLM, strBaseKey & strSubKey, "InstallDate", insDate
If (strValue <> "") and (intRet = 0) Then
objFile.Write strValue & "," & version & "," & insDate & vbCrLf
End If
Next
End Function
I had the same issue with the WMIC command only showing a subset of all installed programs. I needed a command that would output a CSV file of all the installed programs in Windows 10 and I wanted to make it easy for colleagues to run. The following Powershell command seems to work well, running much quicker than the WMIC command; it will generate a CSV in the user's Documents folder called AppsInstalled.csv with programs sorted by name, and with empty lines removed (numerous blank lines were returned in my tests for some reason?).
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate| Where-Object DisplayName -ne $null | Sort-Object -Property DisplayName | Export-Csv "$($env:USERPROFILE)\Documents\AppsInstalled.csv" -NoTypeInformation

Detecting installed programs via registry

I need to develop a process that will detect if the users computer has certain programs installed and if so, what version. I believe I will need a list with the registry location and keys to look for and feed it to the program which is not a problem. Is there a better way to accomplish this?
My first thought was to check in the registry in the uninstallation entries but it seems one of the apps I wish to detect does not have one. What is the standard location for all registry using applications to make an entry in?
On 64-bit systems the x64 key is:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
Most programs are listed there. Look at the keys:
DisplayName
DisplayVersion
Note that the last is not always set!
On 64-bit systems the x86 key (usually with more entries) is:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
User-specific settings should be written to HKCU\Software, machine-specific settings to HKLM\Software. Under these keys, structure [software vendor name]\[application name] (e.g. HKLM\Software\Microsoft\Internet Explorer) may be the most common, but that's just a convention, not a law of nature.
Many (most?) applications also add their uninstall entries to HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\[app name], but again, not all applications do this.
These are the most important keys; however, contents of the registry do not have to represent the installed software exactly - maybe the application was installed once, but then was manually deleted, or maybe the uninstaller didn't remove all traces of it. If you want to be sure, check the filesystem to see if the application still exists where its registry entries say it is.
Edit:
If you're a member of the group Administrators, you can check the HKEY_USERS hive - each user's HKCU actually resides there (you'll need to know the user SID, or go through all of them).
Note: As #Brian Ensink says, "installed" is a bit of a vague concept - are we trying to find what the user could run? Some software doesn't even write to the Registry at all: search for "portable apps" to see apps that have been specifically modified to run directly from media (CD/USB) and not to leave any traces on the computer. We may also have to scan the disks, and network disks, and anything the user downloads, and world-accessible Windows shares in the Internet (yes, such things exist legitimately - \\live.sysinternals.com\tools comes to mind). In this direction, there's no real limit of what the user can run, unless prevented by system policies.
You could use MSI API to enumerate everything installed by Windows Installer but that won't list all the software available on a machine. Without knowing more about what you need I think the concept of "installed" is a little vague. There are many ways to deploy software to a system ranging from big complicated installers to ZIP files and everything in between.
An application does not need to have any registry entry. In fact, many applications do not need to be installed at all. U3 USB sticks are a good example; the programs on them just run from the file system.
As noted, most good applications can be found via their uninstall registry key though. This is actually a pair of keys, per-user and per-machine (HKCU/HKLM - Piskvor mentioned only the HKLM one). It does not (always) give you the install directory, though.
If it's in HKCU, then you have to realise that HKEY_CURRENT_USER really means "Current User". Other users have their own HKCU entries, and their own installed software. You can't find that. Reading every HKEY_USERS hive is a disaster on corporate networks with roaming profiles. You really don't want to fetch 1000 accounts from your remote [US|China|Europe] office.
Even if an application is installed, and you know where, it may not have the same "version" notion you have. The best source is the "version" resource in the executables. That's indeed a plural, so you have to find all of them, extract version resources from all and in case of a conflict decid on something reasonable.
So - good luck. There are dozes of ways to fail.
You can use a PowerShell script to look at registers and get the installed program details. The script bellow will generate a file with the complete list of installed programs. Save it with ".ps" extension and double click the file.
#
# Generates a full list of installed programs.
#
# Temporary auxiliar file.
$tmpFile = "tmp.txt"
# File that will hold the programs list.
$fileName = "programas-instalados.txt"
# Columns separator.
$separator = ","
# Delete previous files.
Remove-Item $tmpFile
Remove-Item $fileName
# Creates the temporary file.
Create-Item $tmpFile
# Searchs register for programs - part 1
$loc = Get-ChildItem HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
$names = $loc |foreach-object {Get-ItemProperty $_.PsPath}
foreach ($name in $names)
{
IF(-Not [string]::IsNullOrEmpty($name.DisplayName)) {
$line = $name.DisplayName+$separator+$name.DisplayVersion+$separator+$name.InstallDate
Write-Host $line
Add-Content $tmpFile "$line`n"
}
}
# Searchs register for programs - part 2
$loc = Get-ChildItem HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
$names = $loc |foreach-object {Get-ItemProperty $_.PsPath}
foreach ($name in $names)
{
IF(-Not [string]::IsNullOrEmpty($name.DisplayName)) {
$line = $name.DisplayName+$separator+$name.DisplayVersion+$separator+$name.InstallDate
Write-Host $line
Add-Content $tmpFile "$line`n"
}
}
# Sorts the result, removes duplicate lines and
# generates the final file.
gc $tmpFile | sort | get-unique > $filename
Seems like looking for something specific to the installed program would work better, but HKCU\Software and HKLM\Software are the spots to look.
In addition to all the registry keys mentioned above, you may also have to look at HKEY_CURRENT_USER\Software\Microsoft\Installer\Products for programs installed just for the current user.
Win32_Product never shows everything, only software installed via an MSI installer (as far as I can tell.)
There are lots of software packages that get installed via other installers that don't show up in there. another way is needed.
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Compatibility Assistant\Persisted

Resources