I'm using Powershell 7.2.5 on macOS 12.5.
I had Google Chrome open and ran Get-Process google*, and got truncated ProcessName:
gps google*
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
0 0.00 626.36 5,529.29 33973 1 Google Chrome
0 0.00 124.94 2,870.73 33993 1 Google Chrome H
Google Chrome H should be Google Chrome Helper or something longer.
gps | sort-object {$_.ProcessName.length} showed that all processnames are truncated to 15 chars.
How do I make Get-Process output without truncation?
I read the help for Get-Process and tried to pipe the output to Format-Custom and Format-List ProcessName -Force, and none of these tricks worked.
Unfortunately, this is not a formatting problem:
At least up to the .NET version underlying PowerShell Core 7.3.0-preview.6, .NET , .NET 7.0.0-preview.6.22324.4, the .ProcessName property value is limited to 15 chars.
This is a known problem, and there is a known fix, but no one has stepped up to implement it yet - see GitHub issue #52860
A - computationally expensive - workaround is to use a call to the native ps utility, via a calculated property:
Get-Process google* |
Select-Object Id,
#{ n='ProcessName'; e={ Split-Path -Leaf (ps -p $_.Id -o comm=) }
Note: The above reports just the process ID and the (full) process name.
More work is needed if you want the same display columns as you would get by default, only with the full process names:
Get-Process google* | ForEach-Object {
$copy = $_ | Select-Object *
$copy.ProcessName = Split-Path -Leaf (ps -p $_.Id -o comm=)
$copy.pstypenames.Insert(0, 'System.Diagnostics.Process')
$copy
}
Related
Our current scenario is this:
We have more than 80 tablet computers (running Windows 10) in our network that run under the same user (DefaultUser). In order to verify that the display settings are correctly set, we would like to use a powershell script to automatically check the used resolution remotely with a support user account.
So far, we know how to get the primary screen resolution for the user under which the script gets executed (which is rather easy):
// get primary screen width
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width // height accordingly
In case we execute this script on one of the tablets using the support account, we get the primary screen resolution for the support account user - but not for the desired user DefaultUser.
How can we get the resolution for the DefaultUser?
The only solution that easily comes to my mind is a rather ugly thing:
Using the windows task scheduler i could create a task that executes the script (under the defaultUser) to get the screen resolution and write the result(s) into a file that can be accessed by the support user account. But i am looking for something more elegant.
It sounds like you're looking for the screen scaling values, based on your code checking the PrimaryScreen values:
# My monitor resolution is 3000x2000
Add-Type -AssemblyName System.Windows.Forms
# [Screen] returns screen size AFTER scaling (200% here)
[System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height
1000
# [SystemInformation] returns the hardware screen resolution (applies to all users)
[System.Windows.Forms.SystemInformation]::VirtualScreen.Height
2000
Scaling can be set on a per-user basis, though there is a machine-wide "Default" setting. Screen scaling is weird, and gets done differently depending on what version of windows you have. Here's how it works in Windows 10 at least.
You can check the current values like so:
# AllUsers setting, which shows as (default)
Get-ItemProperty -path "HKCU:\Control Panel\Desktop\WindowMetrics" | fl AppliedDPI
# User's current scaling setting (I use a * instead of the per-monitor ID)
Get-ItemProperty -path "HKCU:\Control Panel\Desktop\PerMonitorSettings\*" | fl DpiValue
# AppliedDPI shows the "Default" scaling setting on a system level like:
96 : 100%
120 : 125%
144 : 150%
192 : 200% (my default)
# DpiValue shows how many steps up or down the current user's scaling setting is. For example on my machine:
#250%
DpiValue : 2 # +2
#200% (default)
DpiValue : 0
#150%
DpiValue : 4294967294 # -1
#100%
DpiValue : 4294967292 # -3
Finding and overriding scaling for other user profiles is pretty involved, but has been done by other people. I found this script and usage details by user romaliceishimwe2. I have not tested, but it does show how to look at and change other users' profiles:
#First we configure the default, later we will configure any existing users.
#Load the ntuser.dat of the default user
REG LOAD HKU\Default_User C:\users\default\ntuser.dat
#Assign new registry keys
New-ItemProperty -path registry::"HKU\Default_User\Control Panel\Desktop" -Name LogPixels -Value 120 -Type DWord
New-ItemProperty -path registry::"HKU\Default_User\Control Panel\Desktop" -Name Win8DpiScaling -Value 1 -Type DWord
#unload default user ntuser.dat
REG UNLOAD HKU\Default_User
#Here we configure any eixting users
# Regex pattern for SIDs
$PatternSID = 'S-1-5-21-\d+-\d+\-\d+\-\d+$'
# Get Username, SID, and location of ntuser.dat for all users
$ProfileList = gp 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*' | Where-Object {$_.PSChildName -match $PatternSID} |
Select #{name="SID";expression={$_.PSChildName}},
#{name="UserHive";expression={"$($_.ProfileImagePath)\ntuser.dat"}},
#{name="Username";expression={$_.ProfileImagePath -replace '^(.*[\\\/])', ''}}
# Get all user SIDs found in HKEY_USERS (ntuder.dat files that are loaded)
$LoadedHives = gci Registry::HKEY_USERS | ? {$_.PSChildname -match $PatternSID} | Select #{name="SID";expression={$_.PSChildName}}
# Get all users that are not currently logged
$UnloadedHives = Compare-Object $ProfileList.SID $LoadedHives.SID | Select #{name="SID";expression={$_.InputObject}}, UserHive, Username
# Loop through each profile on the machine
Foreach ($item in $ProfileList) {
# Load User ntuser.dat if it's not already loaded
IF ($item.SID -in $UnloadedHives.SID) {
reg load HKU\$($Item.SID) $($Item.UserHive) | Out-Null
}
#####################################################################
# This is where you can read/modify a users portion of the registry
"{0}" -f $($item.Username) | Write-Output
New-ItemProperty -path registry::"HKU\$($Item.SID)\Control Panel\Desktop" -Name LogPixels -Value 120 -Type DWord -force
New-ItemProperty -path registry::"HKU\$($Item.SID)\Control Panel\Desktop" -Name Win8DpiScaling -Value 1 -Type DWord -force
#####################################################################
# Unload ntuser.dat
IF ($item.SID -in $UnloadedHives.SID) {
### Garbage collection and closing of ntuser.dat ###
[gc]::Collect()
reg unload HKU\$($Item.SID) | Out-Null
}
}
You may be able to use CIM or WMI to get from point a to b.
CIM_VideoController is the class that would contain the resolution.
Using PowerShell Core:
Get-CimInstance CIM_VideoController | Select SystemName, CurrentHorizontalResolution, CurrentVerticalResolution
Using Windows PowerShell:
Get-WmiObject Win32_VideoController | Select SystemName, CurrentHorizontalResolution, CurrentVerticalResolution
You should be able to open the proper ports to use either of these options remotely (though you could also probably get away with using Invoke-Command to run them, as long as you use CredSSP or Kerberos Delegation to take the 2nd hop problem into account).
This question shows a Powershell script to generate a list of open File Explorer windows and their path.
My goal is to capture the currently open set of explorer windows, and write out a CMD file with commands like: C:\WINDOWS\explorer.exe /e, "C:\open\this\folder"
So I would like to have the full path and folder name in normal path notation. This is what is showing in titlebar of the Explorer Windows: "C:\open\this\Favorite folder"
The proposed code is:
function Get-WindowTitle($handle) {
Get-Process |
Where-Object { $_.MainWindowHandle -eq $handle } |
Select-Object -Expand MainWindowTitle
}
$app = New-Object -COM 'Shell.Application'
$app.Windows() |
Select-Object LocationURL, #{n='Title';e={Get-WindowTitle $_.HWND}}
As shown above, LocationURL provides a full path in an escaped-URL style:
file:///C:/open/this/Favorite%20%folder"
The #{n='Title';e={Get-WindowTitle $_.HWND}} component produces a column "Title" which is truncated to 5 characters:
C:\...
The full output for one explorer window looks like:
LocationURL Title
----------- -----
file:///C:/open/this/Favorite%20%folder C:...
I found I could avoid the truncation by padding the string 'Title' with many spaces. That string's width seems to determine the maximum width of the output.
Still, I observe that only about 60% of the open explorer windows list a path. The rest are just a blank line.
I tried "$app.Windows() | Select-Object LocationName", but the output only contains the Explorer folder name only, not the full path and folder that is displayed in the Explorer title.
Another mystery is why the script runs so slowly. If I have 10 explorer windows open, the script runs for 30 seconds, taking about 3 seconds per path.
For this script:
function Get-WindowTitle($handle) {
Get-Process |
Where-Object { $_.MainWindowHandle -eq $handle } |
Select-Object -Expand MainWindowTitle
}
$app = New-Object -COM 'Shell.Application'
$app.Windows() |
Select-Object LocationName,#{n=' ------------Title---------------- ';e={Get-WindowTitle $_.HWND}}
This is the output (with some redacting with *** for privacy)
PS C:\E***> .\OpenExplorer.ps1
LocationName ------------Title----------------
------------ ----------------------------------------------------------------------------------
2019-07
Ame****
2019 Priv...
2019-10-3... C:\E\Event Presentations\2019-10-31 Priv**********bcast
E C:\E
5G Brief ... C:\E\Tech************ing\5G Brief (2018)
36 Series...
2019 DE* ... C:\E\16*****N\2019 DE*******************
Newsletters C:\E\Newsletters
Reports C:\E\Tech************ing\Reports
2019-10-2... C:\E\16**********s\2019-10-29 *********************
2019-11 C:\Data\Docs\Stand*********24\2019-11
UB****
Financial... C:\E\Financ************
Expenses C:\E\Internal\Expenses
E C:\E
E***
I assume what you're really interested is the local filesystem paths of the open Explorer windows, not necessarily the window titles (which aren't guaranteed to reflect the full paths).
Somewhat obscurely, the window objects returned by the .Windows() method contain the local path representation in their .Document.Folder.Self.Path property.
(New-Object -ComObject 'Shell.Application').Windows() | ForEach-Object {
$localPath = $_.Document.Folder.Self.Path
"C:\WINDOWS\explorer.exe /e, `"$localPath`""
}
The above produces output such as:
C:\WINDOWS\explorer.exe /e, "C:\Users\jdoe"
C:\WINDOWS\explorer.exe /e, "C:\Program Files"
You can output this to a batch file file as needed, e.g. by appending | Set-Content file.cmd to the above command.
Note: The windows are listed in the order in which they were created, so you cannot infer which among them was most recently activated. See this answer for a solution that finds the topmost File Explorer window and determines the path shown in it.
I found I could avoid the truncation
The truncation is just a display artifact - the data is still there.
You can make the data visible in one of two ways:
pipe to Format-Table -AutoSize to make sure that column values aren't truncated, space permitting
pipe to Format-List, which will show each property on its own line (line-wrapping overly long values).
I would like to check whether Slack is installed on a system or not.
Even though it was installed both of the below two commands giving me blank, why is this?
System details are: Windows 10 64 bit.
$slack32 = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' |
Select-Object DisplayName |
Select-String "Slack" |
Out-String
$slack64 = Get-ItemProperty 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' |
Select-Object DisplayName |
Select-String "Slack" |
Out-String
Slack by default installs under the User's directory, not Program Files, probably does the same in the registry hives.
Try HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
I'm trying to create a very simple script which would check if the a specific program is installed and if so return the version number for that program.
I've been able to get to the point where I'm running the script and able to return a binary value if a program is installed or not but not sure how to return the version number for that installed program.
What I will post will be just what I'm doing to return if program is installed, and need help in then attaining the version number.
function Check_Program_Installed {
$my_check = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, InstallDate |
Format-Table -AutoSize |
Out-String
# Check if Google Chrome is installed
$my_check -Match "Google Chrome"
}
Check_Program_Installed
If you want that function to look for a specific installed program instead of returning a (table) formatted string, then you could simply do:
function Check_Program_Installed {
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory=$true, ValueFromPipeline = $true)]
$Name
)
$app = Get-ItemProperty -Path "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" |
Where-Object { $_.DisplayName -match $Name } |
Select-Object DisplayName, DisplayVersion, InstallDate, Version
if ($app) {
return $app.DisplayVersion
}
}
Check_Program_Installed "Google Chrome"
This will return $null when not found, or the version as string like 70.0.3538.67
Instead of doing the match after formatting the table, you could add a where to select the result you need beforehand and then obtain the DisplayVersion directly from that object. You could clean this up more to do exactly what you need, but here is your code modified to retrieve and display the number if the application is found. Try switching to a bad name to see the else result:
function Get-ApplicationVersion {
$applicationName = "Google Chrome"
$my_check = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, InstallDate | Where -Property DisplayName -Match $applicationName
$versionNumber = $my_check.DisplayVersion
if ($my_check) {
$versionNumber
}
else {
write-warning "Application not found"
}
}
Get-ApplicationVersion
EDITED: Renamed function name from Check_Program_Installed to use PS common verb Get, per suggestion.
function Get-InstalledProgram {
Param (
$ProgramName
)
$UninstallKeys = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
if ( $ProgramName )
{
$UninstallKeys | Where-Object -Property DisplayName -Match -Value $ProgramName | Select-Object DisplayName, DisplayVersion, InstallDate
}
else
{
$UninstallKeys | Select-Object DisplayName, DisplayVersion, InstallDate
}
}
If you wanted to see all the programs, then you don't have to add a parameter. Just pipe its output to Format-Table. Format-Table does some weird just where the items are no longer the objects you're expecting, but table objects. Here is how I would handle that:
Get-InstalledProgram | Format-Table -Autosize
If you want to search for a program, add a parameter. You'll see above I added a parameter for ProgramName. It will match this term to the registry key's DisplayName.
PS C:\> Get-InstalledProgram -ProgramName Java
DisplayName DisplayVersion InstallDate
----------- -------------- -----------
Java 8 Update 181 8.0.1810.25 20180725
Java Auto Updater 2.8.181.13 20180925
If you wanted to just get the version, I would recommend just piping your output to Select-Object -ExpandProperty DisplayVersion
PS C:\> Get-InstalledProgram -ProgramName 'Java 8' | Select-Object -ExpandProperty DisplayVersion
8.0.1810.25
tl;dr
In Windows PowerShell[1] v5.1+, use the following (searches among both 32-bit and 64-bit installed programs, as shown in Control Panel):
Get-Package -ProviderName Programs -IncludeWindowsInstaller '*Google Chrome*' |
ForEach-Object Version
Note: The 32-bit-only HKEY_LOCAL_MACHINE:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall registry key may have more specific entries than what Control Panel shows - I'm unclear on why, but perhaps the composite view in Control Panel is sufficient.
Applied to your example:
PS> (Get-Package -ProviderName Programs -IncludeWindowsInstaller '*Google Chrome*').Version
70.0.3538.67
As for what you tried:
Since you're checking the Wow6432Node registry key branch specifically, you're checking installed 32-bit programs only.
As such, a better name for your function would be Check_32BitProgram_Installed or, more in line with with the function's intent, using an approved PowerShell verb, Get-32BitProgramVersion.
Alternatively, name, the function Get-ProgramVersion and look in both the 32-bit and 64-bit locations and process the results as shown in Theo's and Kevin M. Lapio's and Shawn Esterman's helpful answers:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*,
HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
In line with the generic title of your question, the above is a solution that essentially searches the list of installed applications you would see in Control Panel > Programs > Programs and Features (appwiz.cpl), which covers both 32-bit and 64-bit applications:
Windows PowerShell v5.1 comes with the PackageManagement module and a Programs package provider[1] that allows inspecting installed programs via the Get-Package cmdlet; in PSv3 and PSv4, it is possible to manually install it.
To list installed programs (shown with abridged sample output):
PS> Get-Package -ProviderName Programs -IncludeWindowsInstaller
Name Version Source ProviderName
---- ------- ------ ------------
Git version 2.18.0 2.18.0 Programs
Microsoft Azure Compute Emu... 2.9.8699.20 Programs
Microsoft Azure Authoring T... 2.9.8699.20 Programs
# ...
The output objects are of type [Microsoft.PackageManagement.Packaging.SoftwareIdentity], which have .Name and .Version properties, which enables the solutions above.
The Programs package provider supports two dynamic options (options specific to that provider):
-IncludeWindowsInstaller is needed to make the list of programs reported match what Control Panel shows.
-IncludeSystemComponent, by contrast, reports components that do not show in Control Panel.
[1] Unfortunately, the underlying Programs package provider is not available in PowerShell Core on Windows as of v7.0 - and I'm unclear on whether that is a not-yet situation or whether it will never be - see GitHub issue #13225.
I print outputs like this in my code, but I was told that my outputs needs to be in text(not object). Can somebody explain to me what it means and whats wrong in my code? Thanks.
Write-Output "ALL INSTALLED WINDOWS FEATURES:"
Write-Verbose -Message "Searching installed features..." -Verbose
$obj=Get-WindowsFeature | Where-Object {$_.Installed} | Select-Object Name, InstallState | Format-Table -AutoSize
Write-Output $obj
Write-Output "OPERATING SYSTEM INFO:"
Write-Verbose -Message "Searching operating system info..." -Verbose
$opInfo=Get-CimInstance Win32_OperatingSystem | Select-Object Version, Caption, InstallDate, LastBootUpTime, TotalVirtualMemorySize , SystemDirectory | FL
Write-Output $opInfo
There's nothing technically wrong with your code but personally I would change Write-Output "OPERATING SYSTEM INFO:" to a Write-Verbose, having that header in the output will make working with the results of your function in other parts of the code. However returning the object opInfo rather than a collection of strings is absolutely correct and the only reason someone would be telling you not to is if the code they intend what you are creating to interact with is either written incorrectly or written in an older scripting language like VBS, or if they are just not aware the preference for objects in Powershell development.