Check with powershell if service is installed on multiple computers - powershell

I'm looking for help with my script again :)
I have a script that will query list of servers to find if specific service is installed. This works fine. However, I know that there are some servers in my list that I don't have access to, or there are different credentials. How do I make this visible in output? Because I only get output that service is not installed, which is not true, I just don't have correct credentials.
$name = "BESClient"
$servers = Get-content C:\list.txt
function Confirm-WindowsServiceExists($name)
{
if (Get-Service -Name $name -Computername $server -ErrorAction Continue)
{
Write-Host "$name Exists on $server"
return $true
}
Write-Host "$name does not exist on $server"
return $false
}
ForEach ($server in $servers) {Confirm-WindowsServiceExists($name)}
Also, I'd like to have output formatted into the one line, e.g.:
Server1 Service running
Server2 Service not installed
Server3 no access
etc...
Thanks a lot for any help.

Here's an option which just displays the content of the error on failure:
function Confirm-WindowsServiceExists($name)
{
if (Get-Service -Name $name -Computername $server -ErrorAction SilentlyContinue -ErrorVariable WindowsServiceExistsError)
{
Write-Host "$name Exists on $server"
return $true
}
if ($WindowsServiceExistsError)
{
Write-Host "$server" $WindowsServiceExistsError[0].exception.message
}
return $false
}
As for the second part of the question #arco444 has described the correct approach.

Here's a WMI solution. Any errors you get from attempting to connect to remote computers will be caught with the try/catch blocks. The result of each operation will be stored to a custom object and added to the array that holds the results of all the operations.
$result = #()
$name = "BESClient"
$servers = Get-Content C:\list.txt
$cred = Get-Credential
foreach($server in $servers) {
Try {
$s = gwmi win32_service -computername $server -credential $cred -ErrorAction Stop | ? { $_.name -eq $name }
$o = New-Object PSObject -Property #{ server=$server; status=$s.state }
$result += ,$o
}
Catch {
$o = New-Object PSObject -Property #{ server=$server; status=$_.Exception.message }
$result += ,$o
}
}
$result | Format-Table -AutoSize
You should end up with something like this:
server state
------ -----
s1 running
s4 stopped
s2 The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)

Related

Adding a success or fail message to a script

I have the below very simple script that changes hostname and workgroup to multiple pcs. I want to add to that script a success or fail message but I can't find a way to do that. Can someone help?
$cred = Get-Credential domain\user
$computers = Import-Csv "C:\12.txt" -Header Oldname,Newname
foreach ($name in $computers) {
Add-computer -computername $name.Oldname -workgroupname workgroup -newname $name.Newname -credential $cred -restart -force
}
Append switch -PassThru to the Add-Computer cmdlet.
Normally, this cmdlet does not output anything, but with the PassThru switch, it will return a
ComputerChangeInfo
object from which you can check the .HasSucceeded property.
$cred = Get-Credential domain\user
$computers = Import-Csv -Path "C:\12.txt" -Header Oldname,Newname
foreach ($name in $computers) {
# use splatting on cmdlets that take a lot of parameters
$params = #{
Computername = $name.Oldname
WorkgroupName = $workgroup
NewName = $name.Newname
Credential = $cred
Restart = $true
Force = $true
PassThru = $true
}
try {
$machine = Add-Computer #params -ErrorAction Stop
if ($machine.HasSucceeded) {
Write-Host "Successfully added computer '$($machine.ComputerName)'" -ForegroundColor Green
}
else {
Write-Host "Adding computer '$($machine.ComputerName)' failed!" -ForegroundColor red
}
}
catch {
Write-Warning "Error adding computer $($name.Oldname):`r`n$($_.Exception.Message)"
}
}
You can also experiment with adding switch -Verbose to get more detailed information returned from Add-Computer

It returns all services even though there is one in list

I want my script to take the server and service as a list from a .txt file. After that I want my script to check that if this service exists on the servers on the txt file.
But when I run this script, it returns all of the services as it exists on the server and not the ones I specified in service list. Also it doesn't drop to catch even tho the service doesn't exist.
Can you tell me why it returns all of the services?
$ErrorActionPreference='stop'
$ServerList = Get-Content 'C:\Users\username\Desktop\service test\servers.txt'
$ServiceList = Get-Content 'C:\Users\username\Desktop\service test\services.txt'
try{
foreach($Server in $ServerList){
foreach($Service in $ServiceList){
$Result = Invoke-Command -ComputerName $Server -ScriptBlock {
Get-Service -Name $Service
}
foreach($List in $Result){
Write-Host "$List exists on $Server"
}
}
}
}
catch [System.Management.Automation.ActionPreferenceStopException]
{
Write-Host "Error"
}
Continuing from my comment. . . when using a local variable against a remote computer in a scriptblock, you have to pass it as an argument, or have it referenced using the remote variable of $using.
This is due to a new session being ran on the remote machine with no clue what $service is on that machine as it's never been declared on that side.
You can pass it using the -Arguments, parameter. Creating a param block to pass it onto or, using the remote variable of $using.
Also, there's really no need for you to invoke the command over to the remote machine as Get-Service has a -ComputerName parameter for remote machines:
$ServerList = Get-Content 'C:\Users\username\Desktop\service test\servers.txt'
$ServiceList = Get-Content 'C:\Users\username\Desktop\service test\services.txt'
foreach ($Server in $ServerList)
{
foreach ($Service in $ServiceList.trim())
{
[PSCustomObject]#{
ComputerName = $Server
Service = $Service
Exists = if (Get-Service -Name $service -ComputerName $Server -ErrorAction SilentlyContinue) { $true } else { $false }
}
}
}
as for what you tried:
$Result = Invoke-Command -ComputerName $Server -ScriptBlock {
Get-Service -Name $Service
}
$service (as mentioned above), is empty and not defined on the remote sessions scope.
When you switched it to $using:service it worked but, it returned the type of object and not the name itself, because you're returning the entirety of an object and not the name property. Instead, just reference the current $service that is being used in the loop and declare if it's there, or not.
the script is not structured correctly, an no need to us the invoke command, when you can use 'get-service -computername' Also the try, catch statement would only catch the last error not each.
I changed your original script to reflect this and moved the try, catch statement to catch each error (if the service does not exist).
$ErrorActionPreference='stop'
$ServerList = Get-Content 'C:\temp\servicetest\servers.txt'
$ServiceList = Get-Content 'C:\temp\servicetest\services.txt'
ForEach($Server in $ServerList){
#Get-Service -ComputerName $Server -Name 'XblAuthManager'
ForEach($Service in $ServiceList){
try {
$a = Get-Service -ComputerName $Server -Name $Service
IF ($a) {
Write-Host "Service - $Service exists on Server - $Server"
}
} catch {
Write-Host "Service - $Service does not exist on Server - $Server"
}
}
}

How do I remote into multiple remote computers via PS to discover one specific app and determine the version number on each remote device? No output

FYI: I'm very new to PS and I'm using this as a learning opportunity. Again, I'm trying to find a
specific application on a list of multiple remote devices and determine the version number of the
application on their corresponding host system. I attempted this via a registry query (found this to
be challenging) and then I used Get-WMIObject. As of now, I'm working with this as my script. It's
not producing any output; instead, it returns to the command prompt with no errors or messages.
Script to find specific application and version in multiple remote devices:
$Servers = Get-Content -Path C:\\files\Serverlist.txt
$CIMSession = New-CIMSession -ComputerName $Servers Get-Credentials
$Vendor = "App Name"
foreach($Serv in $Servers) {
If(Test-Connection -ComputerName $Serv -Count 1 -Quiet) {
$Status = Get-Ciminstance Win32_Product -Computername $Serv | Where-object {$_.Version -contains
$Vendor}
if($Status) {
Out-file -Filepath C:\\files\AppVerResults.txt
}
}
}
I also tried adjusting the following section of the script as shown below but it presented me with the error "Get-CimInstance : Access is denied." Is this error message due to group policy or so? I am able to remote into the device corresponding to the message via RDP.
if($Status) {
$Servers + " - "
$Status | Out-file -Filepath C:\\files\AppVerResults.txt
}
}
}
Should I go about it via invoke-command or registry query? I'm slowly picking things up so I'll continue my research but I was hoping to get some advice in the meantime.
I still believe searching the registry is the easier way to go unless you have the specific file path for the .exe.
Use this function to find software on a remote, or local PC. Theres a filter option by specifying -SoftwareName (to look for).
Find-Software -ComputerName Remote_ComputerName -SoftwareName 'SQL'
Also accepts pipeline input, as well as multiple computer names to query for.
Find-Software -ComputerName ComputerOne, ComputerTwo, ComputerThree -SoftwareName 'SQL'
'ComputerOne','ComputerTwo' | Find-Software -SoftwareName 'SQL'
Exporting is also allowed by piping to an Export-* cmdlet.
Heres the code:
Function Find-Software {
[cmdletBinding()]
Param(
[Parameter(Mandatory=$false,
ValueFromPipeLine=$true,
ValueFromPipeLineByPropertyName=$true)]
[Alias('cn','name')]
[string[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory=$false)]
[String]$SoftwareName
)
Begin{
#Get Computer Names to check software version for
$Server_List = Get-Content -Path "C:\files\Serverlist.txt"
#Get Credentials for Script Scope once.
$Credentials = Get-Credential
}
Process{
if($PSBoundParameters.ContainsKey('SoftwareName')){
foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -Credential $Credentials -EnableNetworkAccess -ErrorAction Stop
$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" } -Session $PSSession
$Software_List = $Software_List | Where-Object -FilterScript {$_.DisplayName -match $SoftwareName} | Sort-Object -Property DisplayName
foreach($Software in $Software_List){
if($Software){
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = $Software.DisplayName
" Version " = $Software.DisplayVersion
}
} else {
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = "Not found"
" Version " = $null
}
}
}
} Catch {
"Unable to connect to PC: $Computer"
"Error: $($Error[0].Message.Split('.')[1].Trim())"
}
}
} else {
foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -Credential $Credentials -EnableNetworkAccess -ErrorAction Stop
$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" } -Session $PSSession
$Software_List = $Software_List | Sort-Object -Property DisplayName
foreach($Software in $Software_List){
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = $Software.DisplayName
" Version " = $Software.DisplayVersion
}
}
} Catch {
"Unable to connect to PC: $Computer"
"Error: $($Error[0].Message.Split('.')[1].Trim())"
}
}
} #end ELSE statement
} #end PROCESS block
End {
if(Get-PSSession){
Get-PSSession | Remove-PSSession
}
} #end END block - Perform Session Clean Up
} #end FUNCTION
Simply modify it to fit your needs :)

Remotely registry path controlling via powershell

My conditions are :
if NeedReboot key is exist then result will be Pending reboot
if NeedReboot key is not exist then result will be NO Pending reboot
My desired output :
Computername,Rebootrequired
Host01,Pending reboot
Host02,NO Pending reboot
Script :
$allComputers = Get-Content '.\path\to\computers.txt'
$domainCred = Get-Credential -UserName "domain01\admin01" -Message "Please enter the DOMAIN password"
$Results = foreach($computer in $allComputers) {
$cred = $domainCred
try {
$VmTools = Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Test-Path -Path "HKLM:\SOFTWARE\VMware, Inc.\NeedReboot\"
} -ErrorAction Stop
[PsCustomObject]#{ ComputerName = $computer; RebootRequired = ([int]$VmTools -eq "True") }
}
}
$Results | Format-Table -AutoSize
$Results | Export-Csv -Path 'c:\temp\vmtools.csv' -NoTypeInformation -UseCulture
Try removing the final backslash after the registry path.
Then, Test-Path just returns $true or $false, so converting that to [int] and comparing to the string "True" makes no sense..
Try
[PsCustomObject]#{
ComputerName = $computer
RebootRequired = if($VmTools) {"Pending Reboot") } else {"NO Pending Reboot"}
}
The code is also missing a catch{..} block that should accompany the try {..} block. Something like:
catch {
[PsCustomObject]#{
ComputerName = $computer
RebootRequired = "Error reading registry"
}
}

Error Handling Issue with Try/Catch in Powershell

I have been working on the following example from one of Don Jones' powershell books as part of my personal development and am having some serious trouble getting the try/catch construct to work as it should. As you can see, when the catch block executes, it sets a variable called $everything_ok to $false - which should trigger the else block in the following code. Which it does - the logfile is appended as per my expectations.
However it does not stop the script from ALSO executing the code in the if block and spewing out 'The RPC Server is unavailable' errors when it tries to query the made-up machine 'NOTONLINE' (Exception type is System.Runtime.InteropServices.COMException).
What makes this even stranger is that I went through the script with breakpoints, checking the contents of the $everything_ok variable along the way, and it never contained the wrong value at any point. So why on earth is the if block still executing for 'NOTONLINE' when the condition I have specified ( if ($everything_ok = $true) ) has not been met?
Am I doing something wrong here?
function get-systeminfo {
<#
.SYNOPSIS
Retrieves Key Information on 1-10 Computers
#>
[cmdletbinding()]
param (
[parameter(mandatory=$true,valuefrompipeline=$true,valuefrompipelinebypropertyname=$true,helpmessage="computer name or ip address")]
[validatecount(1,10)]
[validatenotnullorempty()]
[alias('hostname')]
[string[]]$computername,
[string]$errorlog = "C:\retry.txt",
[switch]$logerrors
)
BEGIN {
write-verbose "Error log will be $errorlog"
}
PROCESS {
foreach ($computer in $computername) {
try {$everything_ok = $true
gwmi win32_operatingsystem -computername $computer -ea stop
} catch {
$everything_ok = $false
write-verbose "$computer not Contactable"
}
if ($everything_ok = $true) {
write-verbose "Querying $computer"
$os = gwmi win32_operatingsystem -computername $computer
$cs = gwmi win32_computersystem -computername $computer
$bios = gwmi win32_bios -computername $computer
$props = #{'ComputerName' = $cs.__SERVER;
'OSVersion' = $os.version;
'SPVersion' = $os.servicepackmajorversion;
'BiosSerial' = $bios.serialnumber;
'Manufacturer' = $cs.manufacturer;
'Model' = $cs.model}
write-verbose "WMI Queries Complete"
$obj = new-object -type psobject -property $props
write-output $obj
}
elseif ($everything_ok = $false) {
if ($logerrors) {
"$computer $_" | out-file $errorlog -append
}
}
}
}
END {}
}
get-systeminfo -host localhost, NOTONLINE -verbose -logerrors
The equals sign in Powershell is used as the assignment operation. -eq is used to test for equality. So your if statement is assigning $true to $everything_ok, which then tests true.