I'm looking for a script that will help me uninstall a certain software on several clients in my network.
Right now i'm going through a list, access the client remotely, sign in with my administrator account and uninstalling the software before logging out and repeating the process. All of this is manually so i would like your help to write a powershell script that does these things for me.
Some Problems that might occur:
I can't log in remotely because i can't establish a connection to the client.
Another user might already be logged in on the client.
The software to be uninstalled is actually already uninstalled without my knowledge.
It's somewhere around 900 clients so a script would really help out.
Also, if it would be possible to, after the script is finished, to get a list of which clients that the software was uninstalled on and which clients it weren't would be great.
Questions written like this are likely to elicit 'What have you tried' type responses...
I would recommend using the Windows Installer Powershell Module Uninstall-MSIProduct.
I've described how to use this module remotely in this post: remote PCs using get-msiproductinfo, this example uses Get-MSIProductInfo but could be easily updated to use Uninstall-MSIProduct.
I've had a quick go at changing this to use Uninstall-MSIProduct, but haven't tested it.
[cmdletbinding()]
param
(
[parameter(Mandatory=$true,ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true)]
[string]
$computerName,
[string]
$productCode
)
begin
{
write-verbose "Starting: $($MyInvocation.MyCommand)"
$scriptFolder = Split-Path -Parent $MyInvocation.MyCommand.Path
$moduleName = "MSI"
$modulePath = Join-Path -Path $scriptFolder -ChildPath $moduleName
$remoteScript = {
param($targetPath,$productCode)
Import-Module $targetPath
uninstall-msiproduct -ProductCode $productCode
}
$delayedDelete = {
param($path)
Remove-Item -Path $path -Force -Recurse
}
}
process
{
$remotePath = "\\$computerName\c$\temp\$moduleName"
write-verbose "Copying module to $remotePath"
Copy-Item -Path $modulePath -Destination $remotePath -Recurse -Container -Force
write-verbose "Getting installed products"
Invoke-Command -ComputerName $computerName -ScriptBlock $remoteScript -ArgumentList "c:\temp\$moduleName", $productCode
write-verbose "Starting job to delete $remotePath"
Start-Job -ScriptBlock $delayedDelete -ArgumentList $remotePath | Out-Null
}
end
{
write-verbose "Complete: $($MyInvocation.MyCommand)"
}
Related
Very new to powershell and AD, so apologies if this post has an obvious answer. I have done some research and I am still not finding the answers I am looking for. My script is below for reference.
I have created a simple powershell script that will run on an admin vm i have setup on my domain. I have a separate SQL vm running a backup script that consume a lot of storage over time. I am trying to run this very simple script. My question is, do I need to modify this script in order to store it on my admin vm but have it run on my sql vm? Or can i leave the path as is and just set up in AD task scheduler. I have tried targeting the FQDN and the IP, but it doesn't seem to be working either way.
$backups_file = 'E:\blahBlahBla\SQL\Backups' or
$backups_file = '<IP_ADDRESS>\E:\blahBlahBla\SQL\Backups' or
$backups_file = '<FQDN>E:\blahBlahBla\SQL\Backups'
$backup_file_exist = (Test-Path -Path $backups_file)
if ($backup_file_exist){
# Verifies the folder exists
Write-Output -InputObject "This folder exists"
# returns all the files in the folder.
Get-ChildItem -Path $backups_file
# Deletes all files in the folder that are older that 7 days.
Get-ChildItem -Path $backups_file -Recurse | Where-Object {($_.LastWriteTime -lt (Get-
Date).AddDays(-7))} | Remove-Item
}
else
{
Write-Output -InputObject "Unable to access this directory."
}
Thanks.
well all your $backups_file solutions seems wrong to me.
If you want excess a directory on a Remote system, it has to be at least a fileshare or a administrative share like \\computer\e$\folder\folder\
But why using file shares or something like that when you just simple can connect to a Powershell Session on the Remote Host? here is a example.:
$mySQLServer = "Server1.domain.name", "server2.domain.name"
$backupFolder = "E:\blahBlahBla\SQL\Backups"
foreach ($server in $mySQLServer)
{
$session = New-PSSession -ComputerName $server #maybe -cred if needed
Invoke-Command -Session $session -ArgumentList $backupFolder -ScriptBlock {
param(
$directoy
)
if ($backup_file_exist)
{
# Verifies the folder exists
Write-Output -InputObject "This folder exists"
# returns all the files in the folder.
Get-ChildItem -Path $directoy
# Deletes all files in the folder that are older that 7 days.
Get-ChildItem -Path $directoy -Recurse | Where-Object { ($_.LastWriteTime -lt (Get-Date).AddDays(-7))
} | Remove-Item
}
}
Remove-PSSession
}
Good Luck!
I am having an issue trying to execute the command remotely. First, the command is trying to copy an executable to the remote system and I think that's where my issue is because you can't access \hostname\C$\Windows\Temp straight, you must connect to C$ first then go to C:\Windows\Temp
That being said, I tried that as well ($Dest = "C$"), and still not working
FYI: The folder might or might not exist in the client
$SetupFolder = "C$\Windows\Temp\Logs"
$Path = "C:\Windows\Temp\Logs\Install.exe"
$Dest = "C$"
# Remote run the install for each system
foreach ($System in $SystemList) {
if (test-Connection -Cn $System -quiet) {
Copy-item $Package -Destination \\$System\$SetupFolder -recurse -Force
if (Test-Path - Path $Path) {
Invoke-Command -ComputerName $System -ScriptBlock {powershell.exe $Path /S} -credential $Credentials
Write-Host -ForegroundColor Green "Installation Successful on $System"
}
} else {
Write-Host -ForegroundColor Red "$System is not online, Install failed"
}
}
You could use Enter-PSSession if you cannot use Invoke-Command, this starts an interactive session with a remote computer, you will need to have permissions on the remote computer first to connect,
More information in the PowerShell documentation,
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enter-pssession?view=powershell-7.1
Okay so the script below is originally made using Batch and i converted it to PS. It works at the moment but is there better way to do this?
First we want to check is operating system 32 or 64 bit so we'll get the right installation path. Then we want to check is there old installation folder or not and if there is, the script should stop.
If there isn't that old installation folder, we'll create one and then import the registry file.
After that, we want to change drive H: to C:\Temp and then we'll install the msi-file. When the msi-file is installed, we want to check the installation path is the "program.exe" in the right place.
If everything is ok, we want to create folder for the GCTI files and then copy all the necessary files.
At the end of the script there's couple more file copying left and then we are done.
At the moment this script is in the same folder as the necessary installation files and when we use this to install the program, we need to copy the folder to the remote computer. I am planning to change this script a bit so that at first it asks on which computer we want to install this and then it copies all the files to the specific remote computer and then runs this script in remote computer.
#Let's check is OS 32 or 64 bit
$bit = "C:\Windows\syswow64\."
$isit64bit = Test-Path $bit
If ($isit64bit -eq $True) {$installpath = "C:\Program Files (x86)"}
Else {$installpath = "C:\Program Files"}
#Let's check is there old installation folder
$Program = $installpath+"\Program\"
$Programtest = Test-Path $Program
If ($Programtest -eq $false ) {Write-Host "None found, let's continue the installation"}
Else {Write-Host "Old installation folder found, remove files and try again" Exit}
# Create ODBC-connection in registry
Start-Process -FilePath Reg -ArgumentList import, ".\Progserver_ODBC.reg" -Wait -WindowStyle Minimized
#Let's check if previous action is ok
$registry = "HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBC.INI\Progserver\"
$registrycheck = Test-Path $registry
If ($registrycheck -eq $True) {Write-Host "Registrychange is ok"}
Else {Write-Host "Registrychange failed" Exit}
# Rename Drive "H:" C:\temp
New-PSDrive -Name "H" -PSProvider 'FileSystem' -Root C:\temp
# Install the msi
Start-Process -FilePath msiexec -ArgumentList /i, "Program-4.3.32.msi", /quiet -Wait
$install = "C:\Program Files (x86)\PathtoProgram.exe"
$installcheck = Test-Path $install
If ($installcheck -eq $True) {Write-Host "Installation succeeded"}
Else {Write-Host "Installation failed." Exit}
# Create GCTI's
$GCTI = "$installpath\PathToGCTI\"
If (Test-Path $GCTI) {Write-Host "GCTI folder already exists"}
Else {Write-Host "Create GCTI folder"} New-Item -ItemType Directory -Path $GCTI -Force
Copy-Item .\PathtoGCTI\* -Destination $GCTI -Recurse -Force
Write-Host "Copied GCTI-files"
# Copy program.ini ja vec.ini
Write-Host "Copying program.ini ja vec.ini"
Copy-Item .\PathToProgram.ini $installpath\PathToProgram.ini
Copy-Item .\PathToVec.ini $installpath\PathToVec.ini
# Change folder rights for the installation folder
cacls.exe $installpath\Program /T /E /G "All Users:C"
# Copy files from version 4.3.26
Copy-Item .\PathToProgram.exe $installpath\PathToProgram -Force
# Copy files
Copy-Item .\PathToFiles\* $installpath\PathToProgram\ -Force -Recurse
Set-ItemProperty $installpath\PathToProgram\graph\* -Name isreadonly $true
#Remove PSDrive
Remove-PSDrive -Name "H"
This is by no means exhaustive; just a selection of comments. You might be better submitting this to the Code Review StackExchange site.
General rules:
always used named parameters in PS functions
Your code formatting is important (carriage returns, tabbing/spacing, etc.)
e.g.
### Don't do this
Get-ChildItem "C:\temp"
### Instead do this
Get-ChildItem -Path "C:\temp"
Test if we're running a 64 bit OS and pick the appropriate Program Files folder
if ([environment]::Is64BitOperatingSystem) {
$installationPath = $env:ProgramFiles
}
else {
$installationPath = ${env:ProgramFiles(x86)}
}
When joining file paths, use Join-Path:
### So don't do this
$Program = $installpath+"\Program\"
### Instead do this
$Program = Join-Path -Path $installpath -ChildPath "Program"
When mapping your drive; be careful about scoping.
And probably best ensure you clean up after yourself, too! I have fallen foul of this in the past and it was a nightmare to clean up :-S
try {
if (Test-Path -Path "H:\") {
Remove-PSDrive -Name "H"
}
New-PSDrive -Name "H" -Root "C:\temp" -PSProvider FileSystem -Scope Script
### do your other stuff
catch {
throw $_.Exception
}
finally {
if (Test-Path -Path "H:\") {
Remove-PSDrive -Name "H"
}
}
Note the defensive programming (check if the drive exists already and remove it if it does!).
For bonus points you could pull the mapping code in to a separate function to avoid repetition (D.R.Y.)
Phew... I think that will do for now!
Good work :-)
See below script:
I need to launch this script with admin rights embedded inside of the script to set execution policy to unrestricted and then at the end of the script set it back. From what I've found so far this is either not possible or very difficult to do. I'm hoping there is an easier way to do this. The users that will be running this script do not have admin rights on their PC's so they will not be able to elevate and manually run from inside of powershell.
Stop-process -Name OUTLOOK -ErrorAction SilentlyContinue -Force
Stop-process -Name communicator -ErrorAction SilentlyContinue -Force
Stop-process -Name lync -ErrorAction SilentlyContinue -Force
Stop-Process -Name UcMapi -ErrorAction SilentlyContinue -Force
Stop-Process -Name skypehost -ErrorAction SilentlyContinue -Force
Stop-Process -Name searchprotocolhost -ErrorAction SilentlyContinue -Force
$OstPath = "c:\users\$([environment]::username)"+ "\AppData" + "\local" + "\Microsoft" + "\Outlook"
$ost = get-ChildItem $OstPath | where { $_.Extension -eq ".ost"}
$ost | remove-Item -force
Start-Process Outlook
if (Test-Path 'C:\Program Files (x86)\Microsoft Office\office15\lync.exe')
{
Start-Process 'C:\Program Files (x86)\Microsoft Office\office15\lync.exe'
}
Else
{
write-host "Lync is not installed"
if (Test-Path 'C:\Program Files (x86)\Microsoft Office Communicator')
{
Start-Process 'C:\Program Files (x86)\Microsoft Office Communicator\communicator.exe'
}
Else
{
write-host "Communicator is not installed"
}
}
You can use:
$GBL_Username = "Here type your username"
$GBL_Password = ConvertTo-SecureString –String "Here type your password in plain text" –AsPlainText -Force
$GBL_Credential = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $GBL_Username, $GBL_Password
Start-Process 'C:\Program Files (x86)\Microsoft Office\office15\lync.exe' -Credential $GBL_Credential
And use the variable $GBL_Credential with the second part (the execution of Office Comunicator)
A problem with this: the credential will show in plain text and, if someone try to edit the script with notepad, PowerShell ISE or other program, they can will see the passsword.
Have a good day.
From what I see in the script, there's no need to elevate. If this is only to overcome the ExecutionPolicy than your approach is wrong. ExecutionPolicy is there to prevent users run untrusted scripts. So far your script is one of those.
Correct way of doing it would be to sign your script with the certificate and set your ExecutionPolicy to Allsigned on all computers. Users will then only be able to run the signed scripts from now on.
If this is not possible, I see 2 options:
Users copy contents of the script and paste it into the powershell window
You set ExecutionPolicy to unrestricted. Keep in mind that users will still need to elevate if they try to do something serious, but for this script elevation is not necessary.
So all in all, ExecutionPolicy is there to prevent exactly what you are trying to do, so do not expect it will be easy to overcome. It's also not something that you turn off and on. You should think of what is acceptable for you and set it to appropriate level in your environment.
I need to call a remote VB script from Powershell, and the VB script needs to run on the remote machine.
I have been using \$computer\root\cimv2:Win32_Process").Create(C:\test.vbs)
This works, however I can't get a return value from the script, just a return value from the win32 process.
I would convert the whole thing to powershell, but can't as I'm connecting to a legacy domain I can't install additional tools on so have to call the remote vbscript
This is an old question but I would like to share my solution. It's the same as the one posted by Ansgar but it's been tested and working fine:
$VNC = '\\share\software\AppName\_Install_Silent.vbs'
$Computer = 'RemoteHost'
$TMP = "\\$Computer\c$\TEMP"
if (!(Test-Path $TMP)) {
New-Item -Path $TMP -ItemType Directory
}
Copy-Item -LiteralPath (Split-Path $VNC -Parent) -Destination $TMP -Container -Recurse -Force -Verbose
$LocalPath = Join-Path 'C:\TEMP' (Join-Path (Split-Path $VNC -Parent | Split-Path -Leaf) (Split-Path $VNC -Leaf))
Invoke-Command -ScriptBlock {cscript.exe $Using:LocalPath} -Computer $Computer
# Restart might be needed of remote host
The difference is that you have to copy the files first to the remote machine to avoid the double hop issue and then you can install it with the $Using variable.
Hope this helps someone.
I'd probably try either remote invocation:
Invoke-Command -ScriptBlock { cscript.exe "C:\test.vbs" } -Computer $computer
or PsExec:
PsExec \\$computer cscript.exe "C:\test.vbs"
Can't test either of them right now, though.