Get version of Installed Software on remote machine - powershell

I am trying a write an application to fetch the version of the application installed on remote machines. There is a need to query many remote servers and get the version of the application and show it on the dashboard.
Powershell WMI takes too long to get the information. I am looking for something lot faster.
The app will read remote server information like IP, Username, and password from a config file and fetch the data.
Any help is really appreciated.

It sounds like you want to take a closer look at Powershell Sessions.
There are at least two ways to approach in from there, one is using Invoke-Command in combination with the -ComputerName attribute, possibly along with -Authentication or -Credential. -ScriptBlock contains the code you want to run.
Invoke-Command -ComputerName "computername.domain.local" -ScriptBlock { ... }
I assume from "the application" that your concern is one application, and not every application. Then you should be able to tell the version by running Get-Item on the executable, then look at either VersionInfo.ProductVersion or VersionInfo.FileVersion, whichever is more relevant to your case.
To access one of them, you could use something like:
$version = (Get-Item "path-to-executable\executable.exe').VersionInfo.ProductVersion
To find out which attributes are relevant to your executable, you can run
Get-Item "executable.exe" | Select -ExpandProperty VersionInfo | Format-List *
Combining these techniques, you could try something like this.
# this is a dummy array for example purposes
$computers = #(#{'ip' = '127.0.0.1'; 'username' = 'admin'; 'password' = 'password'})
foreach($computer in $computers)
{
# creating a PSCredential object from plain text passwords is not a good practice, but I'm assuming here that's what you've got to work with
$credentials = [System.Management.Automation.PSCredential]::new($computer.username, (ConvertTo-SecureString -String $computer.password -AsPlainText -Force))
# fetch versioninfo info from remote computer
$versioninfo = Invoke-Command -ComputerName $computer.ip -Credential $credentials -ScriptBlock { Get-Item "executable.exe" | Select -ExpandProperty VersionInfo
if ($versioninfo.ProductVersion -ne '3.1.2414.0')
{
# do something if product version isn't 3.1.2414.0
}
if ($versioninfo.ProductVersionRaw.Major -lt 5)
{
# do something if product version major part is less than 5 (true for 1.5.5.5 but false for 5.1.1.1)
}
}
If you want to run several commands on the client computers, use New-PSSession and pass the session along to every call to Invoke-Command, otherwise you'd lose time and resources opening a new session every time.
Here's an example on how that could be achieved:
$session = New-PSSession -ComputerName $computer.ip -Credential $credentials
$versioninfo = Invoke-Command -Session $session -ScriptBlock { # do something }
if ($versioninfo.ProductVersion -lt 1)
{
Invoke-Command -Session $session -ScriptBlock { # do something else }
}
Remove-PSSession -Session $session
You might also want to check out the using: scope modifier if you find a need to pass variables along to the remote computer, which would make $localvariable visible at the remote computer with $using:localvariable (readonly)
If time is still a concern after this (especially with tcp timeouts on offline hosts), then threading is the next topic you'd want to look into.
As far as I know, my code is compatible with Powershell v3.1, but I recommend using no less than v5, especially on the machine running the script.
This should be enough information to send you on your way. Good luck. :)

Related

Remote CrowdStrike Uninstallation

I am trying to uninstall outdated crowdstrike using CsUninstallTool.exe on bunch of remote servers.
As we know we cannot directly uninstall crowdstrike, it require a maintenance code unique to host. The below script is working correctly for a single remote host but when I try to run same script, crowdstrike did not get uninstalled.
Also same script is asking for credentials after every loop and looking for optimal way to manage user session without asking cred again & again.
$servers = Get-Content 'C:\Users\PP\Desktop\CrowdStrike_Automation\servers.txt'
$maintenance_token = Get-Content 'C:\Users\PP\Desktop\CrowdStrike_Automation\maintenance_key.txt'
foreach ($server in $servers) {
Invoke-Command -ComputerName $server -Credential (Get-Credential) -ScriptBlock {
& "C:\Temp\CrowdStrike_Automation\Setup\CsUninstallTool.exe" MAINTENANCE_TOKEN=$using:maintenance_token /quiet
}
}
Any suggestion?

Issue getting pssessions to loop properly for multiple servers listed in a txt file

I start with a txt file named vms.txt.
It contains 2 servers like so:
server1
server2
When I run the script shown below, the command that is invoked to install VMware tools only runs on server2 and server1 gets skipped. Does anyone have any suggestions on how to modify this script to make it run on both servers in the txt file? I will have to run this in the future for hundreds of VMs, so I am trying to find an easy way to get this to loop properly.
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
foreach($vm in $vms){
$sessions = New-PSSession -ComputerName $vm -Credential $cred
}
foreach($session in $sessions)
{
Invoke-Command -Session $session -ScriptBlock {
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI”
}
}
In your loop-based approach, the problem is your variable assignment:
# !! This only ever stores the *last* session created in $sessions,
# !! because the assignment is performed in *each iteration*.
foreach($vm in $vms){
$sessions = New-PSSession -ComputerName $vm -Credential $cred
}
The immediate fix is to move the assignment out of the loop:
# OK - captures *all* session objects created in $sessions
$sessions = foreach($vm in $vms){
New-PSSession -ComputerName $vm -Credential $cred
}
Taking a step back:
Both New-PSSession -ComputerName and Invoke-Command -Session accept an array of computer names / sessions, so there's no need for loops.
Passing multiple sessions / computer names to Invoke-Command has the big advantage that the operations run in parallel.
Note:
Invoke-Command has built-in throttling to avoid targeting too many machines at once. It defaults to 32, but can be modified with the -ThrottleLimit parameter.
Output from the targeted computers will arrive in no predictable order, but the output objects are decorated with (among others) a .PSComputerName property reflecting the originating computer - see the bottom section of this answer.
That is, your code can be simplified to:
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
$sessions = New-PSSession -ComputerName $vms -Credential $cred
Invoke-Command -Session $sessions -ScriptBlock {
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI”
}
Important:
Sessions should eventually be cleaned up with Remove-PSSession when no longer needed.
However, that stops any commands running in those sessions, so if you've launched asynchronous operations via your Invoke-Command call, you need to ensure that those operations have finished first - see the comments re potentially asynchronous execution of your VMware-tools-10.3.5.exe application below.
Or, even simpler, if you only need to execute one command on each machine, in which case there is no need to create sessions explicitly, pass all computer names directly to Invoke-Command's -ComputerName parameter:
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
# Note the use of -ComputerName
Invoke-Command -ComputerName $vms -Credential $cred -ScriptBlock {
# Note the use of | Write-Output to ensure synchronous execution.
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI” | Write-Output
}
Important:
If your application (VMware-tools-10.3.5.exe) runs asynchronously, you must ensure its
synchronous execution, otherwise it may not run to completion, because the implicitly created remote session is discarded when a script block returns from a given computer.
A simple trick to ensure synchronous execution of any external (GUI-subsystem) executable is to pipe it to Write-Output, as shown above (or Wait-Process, if it doesn't produce console output) - see this answer for an explanation.

How to remotely deploy multiple PowerShell cmdlets/scripts to multiple remote devices?

I'm looking to use a single host server to maintain a PowerShell script, with global variables, that can be interpreted and ran on several other devices in the network cluster.
On the main host I'd like to specifically be able to maintain a list of variables for IP addresses of each other device that I want to run the scripts against, but then how I want to run the script is something I'm having a hard time determining. There are several things I need to do to each other machine in the cluster (change the computer name, modify the time zone and time, configure the network adapters.... there's a decent list of stuff). The commandlets to do the functions on the individual machines is no problem... I have all of that written out and tested. I just don't what my options are for where that script is stored. Preferably, I think I'd like to declare all of the variables for everything that needs to be done on all machines, at the top of the file on the main host. Then I would like to break down everything that needs to be done to each host on the same file, on the main host. I know it will get a little messy, but that would make maintaining the cmdlets for each device much easier, especially when it comes to testing and making changes. Am I trying to do the impossible here??
I learned about using ENABLE-PSSESSION as well as INVOKE-COMMAND, but each seem to have their own challenges. With Enable-PSSession I cannot seem to find a way to wait for the script to connect to each host before it moves on to the next line. I've tried piping in Out-Null, as well as adding a Start-Sleep line. I don't want to have to manually connect to each host and then manually run the list of commands against each host. Invoke-Command doesn't seem to let me break out the SCRIPTBLOCK section into multiple lines.
Is there any suggestion for the best method of accomplishing the desire to run the script from the main host, that performs all of my cmdlets on multiple machines, without any additional human interaction??
Thanks so much!!
-Andrew
EDIT: I found that I can break the ScriptBlock line (contrary to what I thought didn't work yesterday). Here is basically what I'm trying to accomplish, though of course the below does not work when calling the variables from the top of the file:
#Edit These Variables
$NewName_Server2 = "Server2"
$NewName_Server3 = "Server3"
$NewName_Server4 = "Server4"
$IPAddress_Server2 = "10.10.10.2"
$IPAddress_Server3 = "10.10.10.3"
$IPAddress_Server4 = "10.10.10.4"
$TimeZone = "US Eastern Standard Time"
#Do Not Edit These Variables
$Server2 = "192.168.1.2"
$Server3 = "192.168.1.3"
$Server4 = "192.168.1.4"
#Configure Server 2
Invoke-Command -ComputerName $Server2 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server2
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server2
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
#Configure Server 3
Invoke-Command -ComputerName $Server3 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server3
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server3
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
#Configure Server 4
Invoke-Command -ComputerName $Server3 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server3
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server4
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
You can use the using scope to access local variables. I don't know what $local is. Nice try.
$a = 'hi'
invoke-command comp001,comp002 { $using:a }
hi
hi
The other way is using a param, not well documented. Passing arrays is more tricky.
invoke-command comp001,comp002 { param($b) $b } -args $a

How to Properly Put together a Function in Powershell

I have done a ton of research on how to push this EXE to a remote PC using PSSession and all lines of code work when executed line by line. But i have had a hard time putting this into a function that makes sense and will execute all lines of code and successfully install the software with one push of a button. Not sure whats happening. It will install the exe locally when i tried to do put all lines of code in a function and run it. Can you please help instruct what i am doing wrong? Sorry i am a newbie at Powershell.
$dc1 = New-PSSession -ComputerName DC1
Copy-Item C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe -Destination C:\TPAdmin -ToSession $dc1
Enter-PSSession -Session $dc1
Invoke-Command -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"
Remove-Pssession $dc1
Enter-PSSession is for interactive use only, so not suitable for use in a function.[1]
Instead of using Enter-PSSession, pass the session you've created with New-Session to the Invoke-Command command's -Session parameter, which will run the command in the context of that (remote) session.
# Define your function...
function Invoke-InstallerRemotely {
param([string] $ComputerName)
$session = New-PSSession -ComputerName $ComputerName
Copy-Item C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe -Destination C:\TPAdmin -ToSession $session
# Do NOT use Enter-PSSession.
# Pass your session to Invoke-Command with -Session
Invoke-Command -Session $session -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"
Remove-PSSession $session
}
# ... then invoke it.
# Note that functions must be defined *before* they're called.
Invoke-InstallerRemotely -ComputerName DC1
[1] Using it in a function means that an interactive session on the target computer is entered, which you must exit interactively (by typing and submitting exit or Exit-PSSession) before the remaining statements in the function are executed, again locally.
As for …
Sorry i am a newbie at Powershell.
… that's all fine, as we all had to start from somewhere. However... a couple of things here:
Please be sure to format your posts, to encourage folks to want to
help. People frown on no doing that. Having to copy, paste and
reformat your post is well, extra unnecessary work. ;-}. We've al been there.
We have no idea how you are getting up to speed on PowerShell, but
use the freely available resources to limit/avoid all the
misconceptions, frustrations, errors, potential bad habits, etc.,
that you are going to encounter. Live on watching the videos on:
YouTube
Microsoft Virtual Academy
MSDN Channel9
Microsoft Learn
as well as the reference
and eBook resources.
Back to your use case. You do not say what is happening. So, you leave us to guess. Which is not really potentially helpful to you.
Nonetheless, you should just need to do this... in PowerShell v5x as it's require to use the -ToSession argument.
$DC1 = New-PSSession -ComputerName 'DC1'
Copy-Item -ToSession $DC1 -Path 'C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe' -Destination 'C:\TPAdmin'
Invoke-Command -Session $DC1 -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"}
Remove-PSSession -Session $DC1
I am not sure why you are doing that Enter-PSSsssion in the New-PSSession command as it is not needed. It's for standalone interactive sessions.
Explicit PSRemoting = Enter=PSSEssion
Implicit PSREmoting = New-PSSEssion
If all else fails for you on the copy via the session, then just use the normal UNC way to copy from source to destination.
Copy-Item -Path 'C:\temp\Results.csv' -Destination "\\$($DC1.Computername)\c$\temp"
See also:
Copy To or From a PowerShell Session

PowerShell failing to copy from UNC path

I'm working on a script to copy a folder from a UNC path to a local server. I'm remotely running my script through an interactive session and utilizing Invoke-Command -ScriptBlock like so:
Invoke-Command -ComputerName MyServer -ScriptBlock $Script
This is the script to do the copying:
$script {
try {
New-PSDrive -Name MyDrive -PSProvider FileSystem -Root \\uncpathserver\e$\SourceCode\ -Credential Contoso\me
Copy-Item -Path \\uncpathserver\e$\SourceCode\* -Destination E:\Inetpub\Target -Recurse -Force
}
catch {
Write-Host "Failed to copy!"
}
}
It is failing and throwing my catch block every time. I can't seem to figure out what I am missing to get this to work - it seems so simple and I hope I'm not missing something blatantly obvious.
EDIT:
I was able to get it to work by now just running the script from my local PC instead of from a server. I'm calling the file copy out of $script block now as well. This is what the new code looks like:
$MyServers= #("server-01", "server-02")
foreach ($server in $MyServers)
{
$TargetSession = New-PSSession -ComputerName $server -Credential
contoso\me
Copy-Item -ToSession $TargetSession -Path C:\Source\TheCode\ -
Destination "E:\InetPub\wherethecodegoes" -Recurse -Force
}
Everything else I'm doing inside my $script block (which has been omitted here for troubleshooting sake) is working A-OK. I do have to enter my credentials for each server, but due to the small nature of servers I'm working with, that isn't a deal breaker.
Sounds like a 'kerberos double hop' problem.
Short-Answer
Avoid the problem. From your system, setup two PSdrives. Then copy \\uncpathserver\e$\SourceCode\ to \\RemoteIISserver\E$\Inetpub\Target\
Long-Answer
From your system (System A), you are remotely executing a script (on System B) that will copy a remote folder (from System C).
It should work, but it doesn't. This is because when you (specifically, your account) from System A, remotely connects to System B, then asks System C for something, 'System C' doesn't trust you.
A quick google of the problem will show a myriad of ways around this issue, however;
Not all methods are secure (example: CredSSP)
Not all methods will work on your version of Windows (which is...?)
Not all methods will work with PowerShell
One secure method that does work with PowerShell leverages delegation.
This can be a bit daunting to setup, and I suggest you read-up on this thoroughly.
## Module 'ActiveDirectory' from RSAT-AD-PowerShell Windows feature required.
$ServerA = $Dnv:COMPUTERNAME
$ServerB = Get-ADComputer -Identity ServerB
$ServerC = Get-ADComputer -Identity ServerC
Delegate 'Server B' to access 'Server C';
# Set the resource-based Kerberos constrained delegation
Set-ADComputer -Identity $ServerC -PrincipalsAllowedToDelegateToAccount $ServerB
# Confirm AllowedToActOnBehalfOfOtherIdentity.Access is correct (indirectly).
Get-ADComputer -Identity $ServerC -Properties PrincipalsAllowedToDelegateToAccount
Wait about 15 minutes for 'Server B' to sync-up (or just reboot it).
You can force this with the following (Note: $Cred should contain your credentials);
Invoke-Command -ComputerName $ServerB.Name -Credential $cred -ScriptBlock {
klist purge -li 0x3e7
}
Run a test-hop;
Invoke-Command -ComputerName $ServerB.Name -Credential $cred -ScriptBlock {
Test-Path \\$($using:ServerC.Name)\C$
Get-Process lsass -ComputerName $($using:ServerC.Name)
Get-EventLog -LogName System -Newest 3 -ComputerName $($using:ServerC.Name)
}
The downside is you have to setup every remote remote-target (every 'Server C') this way. But the upside is that it's secure.