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?
Related
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. :)
I'm researching whether it's possible to host an always-running Powershell remoting process.
By default, PSRemoting will start a process as the authenticated user.
However, using custom PS Session Configurations, it's possible to set a "run as account" that will ensure that the remote process (on the server, so to speak) always runs as a "known" user, regardless of the identify of the connecting client.
The reason for me researching this, is that I notice that Ansible runs quite slowly against windows servers, and I suspect this is due to the fact that the remoting process on the server gets spun up with each command Ansible sends. I'd like to see if it's possible to have an always-running process that is "ready" in order to speed up executions.
This is as far as I've gotten:
$ansiblecred = get-credential
New-PSSessionConfigurationFile `
-path "C:\sessionconfig.pssc" -SessionType Default `
-RequiredGroups #{ And = 'Administrators' }
Unregister-PSSessionConfiguration -Name ansible -force
Register-PSSessionConfiguration `
-Path "C:\sessionconfig.pssc" -Name ansible `
-RunAsCredential $ansiblecred -AccessMode Remote `
-UseSharedProcess -ThreadOptions ReuseThread
restart-service winrm
$remotecred = Get-Credential
$i = 0
while ($i -lt 10)
{
#This is slow because the remoting session is setup/teard down every time
Invoke-Command -ComputerName "localhost" -Credential $remotecred -Authentication Basic -ScriptBlock {$env:computername} -ConfigurationName ansible
$i ++
}
Even tho I'm connecting to the session with a different credential, the actual process runs as the "service" credential, so that part's good.
However, it seems to be still spinning up and down the process on each execution.
Just for clarification: The client here is not regular Powershell, it's client which will interact directly with the wsman service over http. So while I appreciate all responses, suggestions based around client-side Powershell code (such as new-pssession, invoke-command etc) are not gonna help :-|
Any pointers would be appreciated here, I'm trying to get to a place where the remoting process simply lives regardless of sessions being executed or not. Is this possible?
Create a session outside and use it in the loop.
$Session = New-PSSession -ComputerName $Server -ConfigurationName MyConfiguration
While(1){
Invoke-Command -Session $Session -Credential $remotecred -Authentication Basic -ScriptBlock {'my code'}
sleep 10
}
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.
I already have code written to connect to another computer in my house and pull music files from the C drive. However, I am trying to find out how to keep this code, but modify it in a way that I can use it to run code on the second computer, then save it to a text file.
foreach ($server in Get-Content .\serverList.txt){
psexec \\$server -u username-p password cmd /c dir c:\*.mp3 /s > c:\Powershell\$server.txt
}
You could write a book on PowerShell Remoting (several people have) but it's reasonably straightforward.
On both computers run Enable-PSRemoting to configure all the settings. Then on the originating computer (the one making the remote call) run Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' (if you are security conscious replace the * with the IP of the remote PC).
Then you can run the all-powerful Invoke-Command to do all sorts of awesome stuff remotely. Unless you're on a domain or there's an identical user on the remote PC you'll need to provide credentials which means either prompting for them or saving them, but if I go into too much detail we'll both be here all day. Pretty easy to find the answers on Google.
$cred = Get-Credential
foreach ($server in Get-Content .\serverList.txt) {
Invoke-Command $server -Credential $cred -ScriptBlock { Get-ChildItem C:\*.mp3 -Recurse } | Out-File C:\Powershell\$server.txt
}
I am wondering which is best practice considering both examples will probably work. Using the built in help examples I have written a script to install windows features on remote servers. Here is my code:
$servers = ('server1', 'server2', 'server3', 'server4')
ForEach ($server in $servers) {
Install-WindowsFeature -Name Desktop-Experience -ComputerName $server -IncludeAllSubFeature -IncludeManagementTools -Restart
}
Would the above be preferred OR should I wrap the "Install-WindowsFeature ..." in an "Invoke-Command" block like the following?
Invoke-Command -ComputerName server1, server2, server3, server4 -command {
Install-WindowsFeature -Name Desktop-Experience -ComputerName $server -IncludeAllSubFeature -IncludeManagementTools -Restart
}
Thanks for your insight!
Personally I would use the latter (directly call Install-WindowsFeature -ComputerName $server rather than do a separate Invoke-Command) in this case for the following reasons:
You may be hard-coding the feature names now, but in the future you may want to put those in a variable. If you put them in a variable, you'll have to pass it as a parameter into the Invoke-Command's script block. This is entirely possible, but more work.
By using your own loop, you can write progress messages, logging, etc.
You gain nothing by using Invoke-Command in this case because you're running a single command on the remote computer (as opposed to running multiple commands with -ComputerName parameters vs. running multiple commands inside the script block).