PowerShell terminate all RDP sessions of a user - windows

I need a script that terminates all RDP sessions of an AD user.
Only the username should be given, whereupon the script terminates all RDP sessions of this user (if necessary also enforces them).
Unfortunately, the Get-RDUserSession cmdlet does not work (the ConnectionBroker cannot be found).
Unfortunately, I cannot process the result of the CMD command qwinsta in PowerShell.
Any ideas or tips?
Thank you.

You can create custom objects from qwinsta's output, filter them and use rwinsta to kill the session.
Function Get-TSSessions
{
param (
[Parameter(Mandatory = $true, Position = 0 )]
[String]$ComputerName
) # End Parameter Block
qwinsta /server:$ComputerName |
ForEach-Object{
If($_ -notmatch "SESSIONNAME")
{
New-Object -TypeName PSObject -Property `
#{
"ID" = [Int]$_.SubString(41,05).Trim()
"ComputerName" = $Computer
"User" = $_.SubString(19,22).Trim()
"State" = $_.SubString(47,08).Trim()
}
}
}
} # End Function Get-TSSessions
Get-TSSessions -ComputerName <ServerName> |
Where-Object{$_.User -eq "SomeUser"} |
ForEach{ & "rwinsta /Server:$($_.ComputerName) $($_.ID)" }
Obviously, you can improve by wrapping up the rwinsta command in its own function. At the moment I only have reporting work written around this sort of thing, so in the spirit of answering the question without writing the whole thing, this should get you through.
Also, I believe there are a number of scripts and functions available for this on the PowerShell Gallery. In fact, I think there were functions Get/Stop-TerminalSession in the PowerShell Community Extensions, which you can install as a module.

param
(
[Parameter(Mandatory = $false,
HelpMessage = 'Specifies the user name (SamAccountName).',
DontShow = $false)]
[SupportsWildcards()]
[ValidateNotNullOrEmpty()]
[ValidateScript({
Import-Module -Name 'ActiveDirectory' -Force
if (Get-ADUser -Filter "sAMAccountName -eq '$_'") {
return $true
} else {
return $false
}
})]
[string]$Username = $env:USERNAME
)
$ErrorActionPreference = 'SilentlyContinue'
Import-Module -Name 'ActiveDirectory' -Force
foreach ($system in (Get-ADComputer -Filter ("Name -ne '$env:COMPUTERNAME' -and OperatingSystem -like 'Windows Server*'"))) {
[string]$system = $system.Name
$session = ((quser /server:$system | Where-Object {
$_ -match $Username
}) -split ' +')[3]
if ($session) {
logoff $session /server:$system
}
}

Related

How to get the List of Installed software

I am trying to get the availability of the software on pc. My condition is that I need to fetch whether the application is installed or not on my laptop if it is installed is it in working condition?
# Setting Execution policy for the Current User
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
$currentExecutionPolicy = Get-ExecutionPolicy
Write-Output "The Execution Ploicy is set to $currentExecutionPolicy"
$programFilePath = #(
'Google Chrome-C:\Program Files\Google\Chrome\Application\chrome.exe'
'Brackets Text Editor-C:\Program Files (x86)\Brackets\Brackets.exe'
'Microsoft Edge-C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
'Microsoft Excel-C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE'
#'Microsoft Outlook-C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE'
)
$result = foreach($program in $programFilePath) {
$splitString = $program -split ('-')
$program_name = $splitString[0]
$program_path = $splitString[1]
foreach($program in $program_path) {
if (Test-Path -Path $program) {
# Write-Output "Program Path Exists"
$programProcess = Start-Process -FilePath $program -PassThru -ErrorAction SilentlyContinue
timeout 5
try{
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
timeout 1
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
}
catch{
$runningProcess = Get-Process -Name $programProcess.ProcessName
}
if($runningProcess -eq $true) {
[pscustomobject]#{
Application_Name = $program_name
Application_Availability = 'Installed'
Application_Status = 'Working'
}
}
else {
[pscustomobject]#{
Application_Name = $program_name
Application_Availability = 'Installed'
Application_Status = 'Not Working. Error Log is generated as Application_Error_Log text file.'
}
Get-EventLog -LogName Application | Where-Object {$_.InstanceID -eq '1000'} | Tee-Object -FilePath .\Application_Error_Log.txt
}
<# Action to perform if the condition is true #>
} else {
[pscustomobject]#{
Application_Name = $program_name
Application_Availability = 'Not Installed'
Application_Status = $null
}
}
}
}
" :: System Software Audit Report :: " | Out-File .\System_Software_Details.txt
$result | Tee-Object -FilePath ".\System_Software_Details.txt" -Append
timeout 60
Although I am getting the application active which are working and functional but in my output in Text file application status shows : Application_Status = 'Not Working. Error Log is generated as although my application is working fine
My second concern is I am unable to handle the application which is giving me an error
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
timeout 1
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
I think checking filesystem paths is an option but a bad one - you cannot ensure in any case that the expected path is used. Checking the filesystem is only necessary for portable applications.
A better approach is to check the following registry keys, by doing so you get the same result as it is displayed under add/remove programs:
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -Name DisplayName,DisplayVersion,InstallSource,Publisher,UninstallString
Another way is to query wmi/cim:
Get-CimInstance -query "select * from win32_product"
But by doing so you generate foreach discovered software product an event in the windows event log (Log: Application, Source: MSIInstaller).
To verify if you can start successfully an application by using start-process you need also to specify the parameter -wait and then check if the return code is 0.
$runningProcess -eq $true doesn't necessarily work as $runningProcess is not a boolean but an object. Alas it always returns false.
TL;DR
If you look at your code you see that to get to "...Not Working..." you have to evaluate ($runningProcess -eq $true). Ergo it returns false.
There's always get-package.
$list = '*chrome*','*firefox*','*notepad++*'
$list | % { get-package $_ }
Name Version Source ProviderName
---- ------- ------ ------------
Google Chrome 104.0.5112.102 msi
Mozilla Firefox (x64 en-US) 104.0.1 Programs
Notepad++ (64-bit x64) 7.8.9 msi
Faster as an argument list and no wildcards:
get-package 'google chrome','mozilla firefox (x64 en-us)',
'notepad++ (64-bit x64)'
Or with the threadjob module:
$list = '*chrome*','*firefox*','*notepad++*'
$list | % { start-threadjob { get-package $using:_ } } |
receive-job -wait -auto

How do I generate this report by importing the hostname to a list or csv?

I had this script, but the format was in HTML and I cleaned up all the code and changed the commands that were "gwmi" to "Get-CimInstance" to have good practices. My goal is to transform this script that handles a hostname to a list of hostnames.
Can someone help me?
The idea I had would be to have a window that has a "browse" button to import the list, be it in txt or csv and for each hostname in that list it would do these commands and in the end it would export to an xlsx file (I tried with Export-Excel, but you need to download the module separately, and you need to trust the repository, authorize the import of the module for later use, so I would have to make this standalone, without any request, because I would convert this ps1 into an exe file) and the data, would need to be side by side with the headers, e.g. Hostname, Last User Logged, Type Of Chassis etc.
I would be very grateful if someone can help me, I've been building part 1 of this script for a few hours now, and now I need to go to part 2 (that is this process to create and export csv results) which is the part where I feel stuck.
Add-Type -AssemblyName Microsoft.VisualBasic
$ComputerName = [Microsoft.VisualBasic.Interaction]::InputBox("Insert the hostname Name", "Hardware Report")
If ($ComputerName -eq "")
{
break
}
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
$ProgressPreference = 'SilentlyContinue'
try {
$ErrorActionPreference = "Stop";
$TestComputerHost = Test-Connection $ComputerName -Count 1 -InformationAction Continue -WarningAction SilentlyContinue;
} catch {
#Hostname will be skipped
} finally {
$ProgressPreference = 'Continue'
$ErrorActionPreference = "Continue"
}
#Validade crucial service that is crucial for get remote data, and if is not possible to get this information, the hostname will be skipped
try {
$ErrorActionPreference = "Stop"
Get-CimInstance win32_operatingsystem -ComputerName $ComputerName | Out-Null }
catch [System.Runtime.InteropServices.COMException]
{
#Hostname will be skipped
}
#Validate if the WS Management service is enabled on the remote device
$ProgressPreference = 'SilentlyContinue'
$TestComputerHost = Test-NetConnection $ComputerName -Port 5985 -InformationLevel Quiet -WarningAction SilentlyContinue
If ($TestComputerHost -ne "False"){
}
$ProgressPreference = 'Continue'
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
Function Get-WUChassisType {
[CmdletBinding()]
param (
)
Set-StrictMode -Version 'Latest'
[int[]]$chassisType = try {
$ErrorActionPreference = "Stop";
Get-CimInstance Win32_SystemEnclosure -ComputerName $ComputerName | Select-Object -ExpandProperty ChassisTypes;
} catch {
#Here need to be blank result or skip this result but keep the rest results
} finally {
$ErrorActionPreference = "Continue";
}
switch ($chassisType) {
{ $_ -in 3, 4, 5, 6, 7, 15, 16 } {
return 'Desktop'
}
{ $_ -in 8, 9, 10, 11, 12, 14, 18, 21, 31, 32 } {
return 'Notebook'
}
{ $_ -in 30 } {
return 'Tablet'
}
{ $_ -in 17, 23 } {
return 'Servidor'
}
Default {
}
}
}
#Function to get last logged user on remote computer
Function Get-LastUser {
try {
$ErrorActionPreference = "Stop"
Get-WmiObject Win32_LoggedOnUser -ComputerName $ComputerName |
Select Antecedent -Unique |
% {
$domain = $_.Antecedent.Split('"')[1]
if($domain -eq "DOMAIN") {
"{0}\{1}" -f $domain, $_.Antecedent.Split('"')[3]
}
} | Select-Object -First 1
} catch [System.Runtime.InteropServices.COMException]
{
}
}
#Name of remote computer
$Name = 'Hostname' + $ComputerName
#Get last logged user (by function)
$LastLoggedUser = ((Get-LastUser).Split('\')[1])
#Last Boot Time
$LastBoot = (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName |Select-Object CSName, LastBootUpTime | Select -ExpandProperty LastBootUpTime).tostring("dd/MM/yyyy hh:mm:ss")
#Chassis Type Of Computer
$ChassisType = Get-WUChassisType
#Operating System
$OS = (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).caption
#System Installed On
$SystemInstalledOn = ((Get-CimInstance Win32_OperatingSystem -ComputerName $ComputerName).InstallDate).tostring("dd/MM/yyyy hh:mm:ss")
#Processor
$Processor = (Get-CimInstance win32_processor -ComputerName $ComputerName -filter "deviceid='CPU0'").Name
#Disk
$Disk = (Get-CimInstance Win32_LogicalDisk -ComputerName $ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size
#Ram Memory
$Ram = (Get-CimInstance Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity
#Serial Number
$SerialNumber = Get-CimInstance win32_bios -ComputerName $ComputerName | Select-Object -ExpandProperty SerialNumber
#Manufacturer
$Manufacturer = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Vendor
#Model
$Model = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Name
#Export to CSV
$Name + $LastLoggedUser + $ChassisType + $LastBoot + $OS + $SystemInstalledOn + $Processor + $Disk + $Ram + $SerialNumber + $Manufacturer + $Model | Export-Csv
#Dialog box to information finish script
[Microsoft.VisualBasic.Interaction]::MsgBox("Report is finished", "OKOnly,SystemModal,Information", "Success") | Out-Null```
edit: rewrote it a little for you, try the below.
Note that the input csv expects a header called "ComputerName" and a list of computer names underneath that.
I copied the csv-to-excel part at the bottom from here
#Function to let user select a file then return the filepath.
Function Get-FileName($initialDirectory){
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = “CSV Exports (*.csv)| *.csv”
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.FileName
}
$filepath = Get-FileName -initialDirectory $PSScriptRoot
$csvdata = Import-Csv -Path $filepath
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
Function Get-WUChassisType {
[CmdletBinding()]
param ($ComputerName=$null)
Set-StrictMode -Version 'Latest'
$chassisTypes = Get-CimInstance Win32_SystemEnclosure -ComputerName $ComputerName | Select -ExpandProperty ChassisTypes
switch ($chassisTypes) {
{ $_ -in 3, 4, 5, 6, 7, 15, 16 } {
return 'Desktop'
}
{ $_ -in 8, 9, 10, 11, 12, 14, 18, 21, 31, 32 } {
return 'Notebook'
}
{ $_ -in 30 } {
return 'Tablet'
}
{ $_ -in 17, 23 } {
return 'Servidor'
}
Default {
return ''
}
}
}
#Function to get last logged user on remote computer
Function Get-LastUser($ComputerName){
try {
Get-WmiObject Win32_LoggedOnUser -ComputerName $ComputerName |
Select Antecedent -Unique |
% {
$domain = $_.Antecedent.Split('"')[1]
if($domain -eq "VLINET") {
"{0}\{1}" -f $domain, $_.Antecedent.Split('"')[3]
}
} | Select-Object -First 1
} catch [System.Runtime.InteropServices.COMException]{
}
}
#We'll append all our individual pc resuls into this array
$exportObj = #()
$offlineObj = #()
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
foreach ($row in $csvdata){
if(Test-Connection $row.ComputerName -Count 1){
$ciminfo = Get-CimInstance win32_operatingsystem -ComputerName $row.ComputerName -ErrorAction SilentlyContinue |Select-Object CSName, LastBootUpTime, Caption, InstallDate
$sysinfo = Get-CimInstance win32_ComputerSystemProduct -ComputerName $row.ComputerName -ErrorAction SilentlyContinue | Select-Object Vendor, Name
if ($ciminfo -and $sysinfo){
$objPcResult = New-Object PSObject -Property #{
Name = 'Hostname: ' + $row.ComputerName;
ChassisType = Get-WUChassisType -ComputerName $row.ComputerName;
LastLoggedUser = ((Get-LastUser -ComputerName $row.ComputerName).Split('\')[1]);
LastBoot = $ciminfo.LastBootUpTime.tostring("dd/MM/yyyy hh:mm:ss");
OS = $ciminfo.Caption;
SystemInstalledOn = $ciminfo.InstallDate.tostring("dd/MM/yyyy hh:mm:ss");
Processor = (Get-CimInstance win32_processor -ComputerName $row.ComputerName -filter "deviceid='CPU0'").Name;
Disk = (Get-CimInstance Win32_LogicalDisk -ComputerName $row.ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size;
Ram = (Get-CimInstance Win32_PhysicalMemory -ComputerName $row.ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity;
SerialNumber = Get-CimInstance win32_bios -ComputerName $row.ComputerName | Select-Object -ExpandProperty SerialNumber;
Manufacturer = $sysinfo.Vendor;
Model = $sysinfo.Name;
}
#Add each PC results as a new row in our array
$exportObj += $objPcResult
}
}else{
$objPcResult = New-Object PSObject -Property #{
Name = 'Hostname: ' + $row.ComputerName;
}
$offlineObj += $objPcResult
}
}
#Setup our temp variables to save our collected data as a temporary CSV, so we can import it into Excel to save as an XLSX.
$offlinecsv = "c:\temp\offline.csv" #Location of offline hosts
$tempcsv = "c:\temp\temp.csv" #Location of the source file
$xlsx = "c:\temp\output.xlsx" #Desired location of output
$delimiter = "," #Specify the delimiter used in the file
#Temp export our csv - to be converted to xlsx
$exportObj | Export-Csv -Path $tempcsv -NoTypeInformation
$offlineObj | Export-Csv -Path $offlinecsv -NoTypeInformation
### Create a new Excel Workbook with one empty sheet
$excel = New-Object -ComObject excel.application
$excel.Visible = $false
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)
# Build the QueryTables.Add command and reformat the data
$TxtConnector = ("TEXT;" + $tempcsv)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = $delimiter
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,1 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
# Execute & delete the import query
$query.Refresh()
$query.Delete()
# Save & close the Workbook as XLSX.
$Workbook.SaveAs($xlsx,51)
$excel.Quit()
Here is an example of how I'd make an initial pass at changing your function with the primary changes being:
Multiple calls were being made to the same class, remotely. This is really inefficient. The calls were consolidated to store the result of the first call in a single variable and then reference the variable's properties for the additional information.
Your script is setting preferences, globally, a lot. All PowerShell cmdlets allow you to set the ErrorActionPreference inline, so setting it globally back and forth is unnecessary.
By prestaging an output object ($temp) and emitting it where the code quits processing, you can see the results of partial communications failures (e.g. pingable but WinRM off, etc.).
Your chassis function shouldn't have worked. Since chassisTypes returns an array, you can't use the -in operator to check if an array exists in an array. I used some code from another SO article that shows some pretty cool PowerShell on how to make this value report the chassis values accurately.
The CIM instance of Win32_LoggedOnUser returns the 'domain' and 'name' properties directly so you don't need to string-parse the values using splits and array references.
Examples of using it in the desired states you specified in the question are at the bottom (accepting multiple computer names and accepting them from the contents of a file (not with a popup, but I hope you'll see how not using GUIs will be more helpful)).
For the amount of independent remote calls you're making, if you identify that the code runs slower than you'd like, you might think about adapting it to use Invoke-Command. This would pass all of your code to the remote machine once, process it on the remote machine, and just return the output object. In my experience, this dramatically reduces the execution time of the script (e.g. I was able to pull information from thousands of servers spanning the globe in about 15 minutes using Invoke-Command, whereas individual remote calls took 12 hours or more).
Function Generate-ComputerHwReport {
param(
[Parameter(Mandatory=$true)] [string[]] [ValidateNotNullOrEmpty()] $ComputerNames ## This lines requires the passed in value to be an array of strings
)
## Modification -- Looping through the array to check all computers passed in
foreach ($ComputerName in $ComputerNames) {
$temp = [pscustomobject] #{
TestPing = $false
TestWinRM = $false
TestWSMan = $false
Hostname = $ComputerName
LastUser = ''
LastBootTime = ''
ChassisType = ''
OS = ''
InstallDate = ''
Processor = ''
Disk = ''
Ram = ''
SerialNumber = ''
Manufacturer = ''
Model = ''
ErrorLog = ''
}
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
if (Test-Connection $ComputerName -Count 1 -Quiet) {
$temp.TestPing = $true
} else {
$temp
continue
}
#Validade crucial service that is crucial for get remote data, and if is not possible to get this information, the hostname will be skipped
try {
$Win32_OS = Get-CimInstance win32_operatingsystem -ComputerName $ComputerName -ErrorAction Stop
$temp.TestWinRM = $true
$temp.LastBootTime = $Win32_OS.LastBootUpTime.ToString("dd/MM/yyyy hh:mm:ss")
$temp.OS = $Win32_OS.Caption
$temp.InstallDate = $Win32_OS.InstallDate.ToString("dd/MM/yyyy hh:mm:ss")
} catch {
#Hostname will be skipped
$temp
continue
}
#Validate if the WS Management service is enabled on the remote device
if ((Test-NetConnection $ComputerName -Port 5985).TcpTestSucceeded) {
$temp.TestWSMan = $true
} else {
$temp
continue
}
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
## https://stackoverflow.com/questions/55184682/powershell-getting-chassis-types-info
$ChassisTypes = #{
Name = 'ChassisType'
Expression = {
# property is an array, so process all values
$result = foreach($value in $_.ChassisTypes)
{
switch([int]$value)
{
1 {'Other'}
2 {'Unknown'}
3 {'Desktop'}
4 {'Low Profile Desktop'}
5 {'Pizza Box'}
6 {'Mini Tower'}
7 {'Tower'}
8 {'Portable'}
9 {'Laptop'}
10 {'Notebook'}
11 {'Hand Held'}
12 {'Docking Station'}
13 {'All in One'}
14 {'Sub Notebook'}
15 {'Space-Saving'}
16 {'Lunch Box'}
17 {'Main System Chassis'}
18 {'Expansion Chassis'}
19 {'SubChassis'}
20 {'Bus Expansion Chassis'}
21 {'Peripheral Chassis'}
22 {'Storage Chassis'}
23 {'Rack Mount Chassis'}
24 {'Sealed-Case PC'}
default {"$value"}
}
}
$result
}
}
$temp.ChassisType = (Get-CimInstance -ClassName Win32_SystemEnclosure -ComputerName $ComputerName | Select-Object -Property $ChassisTypes).ChassisType
#Function to get last logged user on remote computer
try {
$t = Get-CimInstance win32_loggedonuser -ComputerName $ComputerName -ErrorAction Stop | Select Antecedent -Unique
$temp.LastUser = "{0}\{1}" -f $t.Antecedent.Domain, $t.Antecedent.Name
} catch {
$temp.ErrorLog += $_
}
#Processor
$temp.Processor = (Get-CimInstance win32_processor -ComputerName $ComputerName -filter "deviceid='CPU0'").Name
#Disk
$temp.Disk = ((Get-CimInstance Win32_LogicalDisk -ComputerName $ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size) -join ', '
#Ram Memory
$temp.Ram = ((Get-CimInstance Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity) -join ', '
#Serial Number
$temp.SerialNumber = Get-CimInstance win32_bios -ComputerName $ComputerName | Select-Object -ExpandProperty SerialNumber
#Manufacturer
$temp.Manufacturer = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Vendor
#Model
$temp.Model = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Name
$temp
}
}
## Output to console
Generate-ComputerHwReport -ComputerNames localhost, pc2
## Output to console reading in the computer names from a file
Generate-ComputerHwReport -ComputerNames (gc listofcomputernames.txt)
## Output to CSV reading in the computer names from a file
Generate-ComputerHwReport -ComputerNames (gc listofcomputernames.txt) | Export-Csv -NoTypeInformation ComputerHwReport.csv

Powershell: Find installed Antivirus & state, filtering out Windows Defender

I came across the basis of this script in another post here, however, I would like to take it a bit further and have been experimenting. What I am seeking to achieve is to get the name, state of the antivirus installed on the device and of course I want to filter out Windows Defender. Here is what I have so far...
The issue I have with the current code that I am not sure how to get around is that I am getting the state code for Windows Defender also.
I would greatly appreciate your advise and assistance.
clear
function Get-AntivirusName {
[cmdletBinding()]
param (
[string]$ComputerName = "$env:computername" ,
$Credential
)
$wmiQuery = "SELECT * FROM AntiVirusProduct"
$AntivirusProduct = Get-WmiObject -Namespace "root\SecurityCenter2" -Query $wmiQuery #psboundparameters
[array]$AntivirusNames = $AntivirusProduct.displayName | sort -unique
[array]$AntivirusState = $AntivirusProduct.productState | sort -unique
$AntivirusState
Switch($AntivirusNames) {
{$AntivirusNames.Count -eq 0}{"Anti-Virus is NOT installed!";Continue}
{$AntivirusNames.Count -eq 1 -and $_ -eq "Windows Defender"} {Write-host "ONLY Windows Defender is installed!";Continue}
{$_ -ne "Windows Defender"} {"Antivirus Product(s): $_."}
}
}
Get-AntivirusName
If you want to rule out Windows Defender, but do want to get a console message, I would change the function like below:
function Get-AntivirusName {
[cmdletBinding()]
param (
[string]$ComputerName = $env:COMPUTERNAME,
$Credential
)
$wmiQuery = "SELECT * FROM AntiVirusProduct"
$AntivirusProduct = #(Get-CimInstance -Namespace "root\SecurityCenter2" -Query $wmiQuery #psboundparameters)
if ($AntivirusProduct.Count -eq 0) {
Write-Host 'Anti-Virus is NOT installed!' -ForegroundColor Red
}
elseif ($AntivirusProduct.Count -eq 1 -and $AntivirusProduct.displayName -like '*Windows Defender*') {
Write-Host 'ONLY Windows Defender is installed!' -ForegroundColor Cyan
}
else {
# filter out Windows Defender from the list
$AntivirusProduct = $AntivirusProduct | Where-Object {$_.displayName -notlike '*Windows Defender*'} | Sort-Object -Unique
# output objects with both the product name and the status
foreach ($avProduct in $AntivirusProduct) {
[PsCustomObject]#{
AV_Product = $avProduct.displayName
AV_Status = $avProduct.productState
}
}
}
}
Get-AntivirusName

How to splat to a function in powershell?

I have a function (which started out as a ScriptBlock) and I'd like to be able to splat my input parameters to store configurations, but instead I'm passing each of them individually and this is bad, because I'd rather just pass the hashmap when I want to run the script block using Invoke-Command -Command ${function:sb} -ArgumentList $service_host_download_11_1_1
or
Invoke-Command -Command ${function:sb} -ArgumentList $service_host_11_1_1
But thus far I have been unable to achieve this; see the code below, is there a way to do this?
# I'd like to be able to splat these to my sb function
$service_host_download_11_1_1 = #{
packageName = 'ServiceHostDownload'
searchPattern = 'VERSION,'
host_arr = $(Get-ADComputer -LDAPFilter '(&(name=MCDONALDS*)(!(name=MCDONALDSTAB*)))' -SearchBase "OU=Simphony,OU=MCDONALDS,OU=Florda,OU=Places,DC=mckeedees,DC=com" | Select-Object -ExpandProperty Name)
}
# And this also
$service_host_11_1_1 = #{
packageName = 'ServiceHost'
searchPattern = 'VERSION,|REBOOT'
host_arr = $(Get-ADComputer -LDAPFilter '(&(name=MCDONALDS*)(!(name=MCDONALDSTAB*)))' -SearchBase "OU=Simphony,OU=MCDONALDS,OU=Florda,OU=Places,DC=mckeedees,DC=com" | Select-Object -ExpandProperty Name)
}
function sb($host_arr,
$packageName,
$searchPattern) {
$host_arr | % {
Invoke-Command -ComputerName $_ -AsJob -ArgumentList #($packageName, $searchPattern) -ScriptBlock {param($pn, $sp)
$result = gc -path "C:\Micros\Simphony\CALTemp\Packages\$($pn)\Setup_log.txt" | Select-String -Pattern $sp
Write-Host "$($ENV:COMPUTERNAME),$($pn),$($result)"
}
}
}
# I'd rather be splitting with Invoke-Command here.
sb $service_host_download_11_1_1['host_arr'] $service_host_download_11_1_1['packageName'] $service_host_download_11_1_1['searchPattern']
# Wait for completion
Start-Sleep -Seconds 5000
Get-Job | ? { $_.State -eq 'Completed'} | Receive-Job
# Write out failed
#Get-Job | ? { $_.State -eq 'Failed'} | Receive-Job
# Set us up for next time...
Get-Job | Remove-Job

Have I written this PowerShell script well enough to balance simplicity and performance?

I have a script that checks remote servers for tomcat and the associated java versions. It takes about 60 seconds to run against a list of about 16 servers. I'm just curious if the script is as efficient as realistically possible. I'm far from a PowerShell pro but I'm satisfied with the outcome. Just checking for where there is room for improvement.
$Servers = 'server1','server2','etc'
$Output = #()
foreach ($Server in $Servers)
{
$SName = gwmi -Class Win32_Service -ComputerName $Server -Filter {Name LIKE 'Tomcat%'}
IF ($SName -ne $null) {
$Output += [PSCustomObject]#{
Server_name = $SName.PSComputerName
Service_name = $SName.Name
Service_status = $SName.State
Tomcat_version = "$(Get-Content -Path ("\\"+$SName.PSComputerName+"\"+"$($SName.PathName.ToString())".Substring(0,$SName.Pathname.LastIndexOf("\")-3)+"\webapps\ROOT\RELEASE-NOTES.txt" -replace ":", "$") | Select-String -Pattern 'Apache Tomcat Version ')".TrimStart()
Java_Version = (Invoke-Command -ComputerName $Server -ScriptBlock {(GCI -Path "$((Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\Tomcat9\Parameters\Java').Jvm)").VersionInfo.ProductName})
}
}
Else {}
}
$Output | Select Server_name, Service_name,Service_status, Tomcat_Version, Java_Version | Format-Table -AutoSize
Can I simplify things anymore?
Is the time to completion decent for what is being performed?
Invoke-Command allows you to connect with multiple computers at the same time which should be more efficient.
+= is pretty bad, please read: Why should I avoid using the increase assignment operator (+=) to create a collection
You're querying WMI first and then if service is there you are using Invoke-Command, mind as well, connect once to the remote host and check everything.
I personally would do something like this
$Servers = 'server1','server2','etc'
$scriptBlock = {
# Since you're querying each server, and then if the service is there you Invoke-Command,
# mind as well Invoke-Command at first and if the service is there enter the If condition,
# else close the connection.
$tomcatServ = Get-CimInstance -Class Win32_Service -Filter "Name LIKE 'Tomcat%'"
if($tomcatServ)
{
##### This part is pretty confusing for someone reading your code, if you show us how does
##### RELEASE-NOTES.txt looks we may be able to improve it and simplified it a bit
$path = $tomcatServ.PathName.Substring(0,$tomcatServ.PathName.LastIndexOf("\")-3)
$path = Join-Path $path -ChildPath "webapps\ROOT\RELEASE-NOTES.txt"
$tomCatVer = ((Get-Content $path) -replace ":", "$" | Select-String -Pattern 'Apache Tomcat Version ').TrimStart()
##### This part is a bit confusing too
$key = 'HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\Tomcat9\Parameters\Java'
$javaVer = GetChild-Item -Path ((Get-ItemProperty -Path $key).Jvm).VersionInfo.ProductName
#####
[PSCustomObject]#{
Server_name = $env:ComputerName
Service_name = $tomcatServ.Name
Service_status = $tomcatServ.State
Tomcat_version = $tomCatVer
Java_Version = $javaVer
}
}
}
$Output = Invoke-Command -ComputerName $Servers -ScriptBlock $scriptBlock -HideComputerName
$Output | Select-Object * -ExcludeProperty RunspaceID | Format-Table -AutoSize

Resources