Because of a messed up group policy object, multiple computers have TightVNC installed when they shouldn't. The GPO is gone, so just removing the software from there isn't an option that I'm aware of. Therefore, I'm scripting things in order to remove PowerShell from a list of computers.
This is my script:
if ($args.length -ne 1) {
Write-Warning "Must pass computer name, ending script.";
break
}
$pc = $args[0]
Write-Output "Scanning $pc for TightVNC...."
$prod = wmic /node:$pc product get name | where {$_ -match "TightVNC"}
if ($prod) {
Write-Output "Found TightVNC, attempting uninstall...."
wmic /node:$pc product where name="TightVNC" call uninstall
} else {
Write-Warning "Could not find TightVNC on $pc."
}
Write-Output "Done."
Now, my output is as follows:
Scanning [computer] for TightVNC....
Found TightVNC, attempting uninstall....
ERROR:
Description = Invalid query
Done.
However, if I copy and paste the second wmic line into an elevated command prompt and replace $pc with [computer], it works just fine. My PowerShell window is elevated.
Does anyone know why my script would be having a fit about this? I know that it does take quite a long time for the first wmic command to complete (>=5 minutes), but it does as well in the second command window where it actually works. I'd appreciate any insight into this.
NOTE: I am using wmic because the computers here aren't properly configured for remote PowerShell access. It's on my list of things to do.
You're running afoul of PowerShell's string parsing. Try this instead:
wmic /node:$pc product where name=`"TightVNC`" call uninstall
Note, for those on PowerShell V3, you can use:
wmic /node:$pc --% product where name="TightVNC" call uninstall
Here's an answer from http://www.tinyint.com/index.php/2011/04/20/escaping-quotes-in-powershell-exe-command-via-command-prompt/ that worked for me:
wmic /node:$pc product where 'name=\"TightVNC\"' call uninstall
Related
Is there any sane, reliable contract that dictates whether Write-Host is supported in a given PowerShell host implementation, in a script that could be run against any reasonable host implementation?
(Assume that I understand the difference between Write-Host and Write-Output/Write-Verbose and that I definitely do want Write-Host semantics, if supported, for this specific human-readable text.)
I thought about trying to interrogate the $Host variable, or $Host.UI/$Host.UI.RawUI but the only pertinent differences I am spotting are:
in $Host.Name:
The Windows powershell.exe commandline has $Host.Name = 'ConsoleHost'
ISE has $Host.Name = 'Windows PowerShell ISE Host'
SQL Server Agent job steps have $Host.Name = 'Default Host'
I have none of the non-Windows versions installed, but I expect they are different
in $Host.UI.RawUI:
The Windows powershell.exe commandline returns values for all properties of $Host.UI.RawUI
ISE returns no value (or $null) for some properties of $Host.UI.RawUI, e.g. $Host.UI.RawUI.CursorSize
SQL Server Agent job steps return no values for all of $Host.UI.RawUI
Again, I can't check in any of the other platforms
Maintaining a list of $Host.Name values that support Write-Host seems like it would be bit of a burden, especially with PowerShell being cross-platform now. I would reasonably want the script to be able to be called from any host and just do the right thing.
Background
I have written a script that can be reasonably run from within the PowerShell command prompt, from within the ISE or from within a SQL Server Agent job. The output of this script is entirely textual, for human reading. When run from the command prompt or ISE, the output is colorized using Write-Host.
SQL Server jobs can be set up in two different ways, and both support capturing the output into the SQL Server Agent log viewer:
via a CmdExec step, which is simple command-line execution, where the Job Step command text is an executable and its arguments, so you invoke the powershell.exe executable. Captured output is the stdout/sterr of the process:
powershell.exe -Command x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
via a PowerShell step, where the Job Step command text is raw PS script interpreted by its own embedded PowerShell host implementation. Captured output is whatever is written via Write-Output or Write-Error:
#whatever
Do-WhateverPowershellCommandYouWant
x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
Due to some other foibles of the SQL Server host implementation, I find that you can emit output using either Write-Output or Write-Error, but not both. If the job step fails (i.e. if you throw or Write-Error 'foo' -EA 'Stop'), you only get the error stream in the log and, if it succeeds, you only get the output stream in the log.
Additionally, the embedded PS implementation does not support Write-Host. Up to at least SQL Server 2016, Write-Host throws a System.Management.Automation.Host.HostException with the message A command that prompts the user failed because the host program or the command type does not support user interaction.
To support all of my use-cases, so far, I took to using a custom function Write-Message which was essentially set up like (simplified):
$script:can_write_host = $true
$script:has_errors = $false
$script:message_stream = New-Object Text.StringBuilder
function Write-Message {
Param($message, [Switch]$iserror)
if ($script:can_write_host) {
$private:color = if ($iserror) { 'Red' } else { 'White' }
try { Write-Host $message -ForegroundColor $private:color }
catch [Management.Automation.Host.HostException] { $script:can_write_host = $false }
}
if (-not $script:can_write_host) {
$script:message_stream.AppendLine($message) | Out-Null
}
if ($iserror) { $script:has_errors = $true }
}
try {
<# MAIN SCRIPT BODY RUNS HERE #>
}
catch {
Write-Message -Message ("Unhandled error: " + ($_ | Format-List | Out-String)) -IsError
}
finally {
if (-not $script:can_write_host) {
if ($script:has_errors) { Write-Error ($script:message_stream.ToString()) -EA 'Stop' }
else { Write-Output ($script:message_stream.ToString()) }
}
}
As of SQL Server 2019 (perhaps earlier), it appears Write-Host no longer throws an exception in the embedded SQL Server Agent PS host, but is instead a no-op that emits nothing to either output or error streams. Since there is no exception, my script's Write-Message function can no longer reliably detect whether it should use Write-Host or StringBuilder.AppendLine.
The basic workaround for SQL Server Agent jobs is to use the more-mature CmdExec step type (where Write-Output and Write-Host both get captured as stdout), but I do prefer the PowerShell step type for (among other reasons) its ability to split the command reliably across multiple lines, so I am keen to see if there is a more-holistic, PowerShell-based approach to solve the problem of whether Write-Host does anything useful for the host I am in.
Just check if your host is UserInteractive or an service type environment.
$script:can_write_host = [Environment]::UserInteractive
Another way to track the output of a script in real time is to push that output to a log file and then monitor it in real time using trace32. This is just a workaround, but it might work out for you.
Add-Content -Path "C:\Users\username\Documents\PS_log.log" -Value $variablewithvalue
I'm trying to write a PowerShell script that will give me a list if of roles and features if run on a server but if run on a client machine will say "Only able to execute command on a server."
I've played around with this script a lot and can get it to run on either a client machine or server (depending on what I've tweaked) but not both. Here's the latest iteration:
$MyOS="wmic os get Caption"
if("$MyOS -contains *Server*") {
Get-WindowsFeature | Where-Object {$_. installstate -eq "installed"
}}else{
echo "Only able to execute command on a server."}
What am I doing wrong?
The quotes around your wmic command will create the $MyOS variable with a String and not execute the command. Still, I would recommend you use native PowerShell commands such as Get-CimInstance. Like the $MyOS variable your if statement condition will always equal true as the quotes will make it a String.
$MyOS = Get-CimInstance Win32_OperatingSystem
if ($MyOS.Caption -like "*Server*") {
Get-WindowsFeature | Where-Object { $_. installstate -eq "installed" }
}
else {
Write-Output "Only able to execute command on a server."
}
You can also use the ProductType property. This is a (UInt32) number with the following values:
1 - Work Station
2 - Domain Controller
3 - Server
$MyOS = (Get-CimInstance Win32_OperatingSystem).ProductType
if ($MyOS -gt 1) {
Get-WindowsFeature | Where-Object { $_. InstallState -eq "installed" }
}
else {
Write-Output "Only able to execute command on a server."
}
Try to use '-like' instead of 'contains', it should work
Generally, I try to avoid pre-checks like this that make assumptions about functionality that may not be true forever. There's no guarantee that Get-WindowsFeature won't start working on client OSes in a future update.
I prefer to just trap errors and proceed accordingly. Unfortunately, this particular command produces a generic Exception rather than a more specifically typed exception. So you can't really do much other than string matching on the error message to verify specifically what happened. But there's very little that can go wrong with this command other than the client OS error. So it's pretty safe to just assume what went wrong if it throws the exception.
try {
Get-WindowsFeature | Where-Object { $_. InstallState -eq "installed" }
} catch {
Write-Warning "Only able to execute command on a server."
}
If you don't want to accidentally hide an error that's not the client OS one, change the warning message to just use the actual text from the error. This also gets you free localization if you happen to be running this code in a location with a different language than your own.
Write-Warning $_.Exception.Message
I am building a script to automate computer build and configuration: The idea is that from WDS it comes as clean as possible, automatically runs this script which will check the serial number, query our Workday database of assets and configure the OS according to what the user assigned to that system needs.
Right now I am focusing on 3 big groups: Laptop, Desktop, and Lab. All 3 will have some SW that will be the same and some that will be specific for each. My issue is with msiexec: Initially, I hard-coded all the installations for each group. but this means that I will have to change the script each time something is updated (say a new app is rolled out as default). which is not ideal.
function Install-Desktop {
#Write-Output "Here will be the install Desktop computer script"
$IPATH="<Path To root sw folder>"
#Software List
<# SOFTWARE LIST #>
$office="$IPATH\script\o365"
$webex="$IPATH\script\webex"
$chrome="$IPATH\script\chrome"
#install Ofice:
Invoke-Expression "$office\setup.exe /configure $office\O365.xml"
$params = '/i', "$webex\webexapp.msi",'/qb!','/norestart'
Start-Process msiexec -ArgumentList "$params" -Wait -PassThru
$params = '/i', "$chrome\GoogleChromeStandaloneEnterprise64.msi",'/qb!','/norestart'
Start-Process msiexec -ArgumentList $params -Wait -PassThru
}
This piece of code works well.
Now my idea was to import from a list the software to be installed (it is easier to maintain a list than to modify the script every time). something like:
function install-software {
param (
[String]$Type
)
$IPATH=<ROOT SW Folder>
$SoftWares=Import-Csv -Path "$IPath\script\$Type`.csv" #there will be a Laptop.csv in that path
foreach ($Software in $SoftWares) {
#detect if it is msiexect or other:
# (this has to do with how the csv is built, the first parameter is '/i' if it is an msi installer)
if ($Software.param1 -eq "'/i'") {
Start-Process msiexec -ArgumentList $Software -Wait -PassThru
}
else {
$Params=[string]::Join(" ",$Software.param1,$Software.param2,$Software.param3,$Software.param4)
Invoke-Expression "$Params"
}
}
}
This only works on the else part. However on the msiexec side of the if, the MSI opens as without arguments. I tried a lot of ways to pass the args, none worked. I am not a PowerShell guru in any way, so there is probably something that I am missing to see here.
Well, it looks like you have to pass the full path, it doesn't even let you use mounted net drive: so the answer was on the csv. instead of S:\<path to installer> it had to be \<Full path to installer> and i had to get rid of all the quotes and double quotes as well.
lately i've been making a script to find the next available computername in AD. I got it working but when im reinstalling windows on a computer with task sequence it find a new name even tho it allready got a name i want. I tried adding a step in the powershell script:
if ($env:computername -like "Computer*")
{
exit
}
This does not work. What is the variable that find the current computername in PE? I use "OSDComputername" to set the computername.
I'm running the following command from within a Microsoft System Centre Orchestrator PowerShell activity:
Install-WindowsFeature -ConfigurationFilePath C:\DeploymentConfigTemplate.xml -ComputerName ServerXYZ
the command isn't doing what it's supposed to do, and I want to be able to return if the command was successful or not, and any error message if possible. Ignore the fact it's running in Orchestrator, as I'm more concerned about the PowerShell question. When I run the command from ISE it does what it's supposed to do, that's why I want to see what is returned from PowerShell.
Thanks.
It's hard to know what may be happening without more context. The following will record any errors encountered in an xml file that you can import later with import-clixml:
Install-WindowsFeature -ConfigurationFilePath C:\DeploymentConfigTemplate.xml -ComputerName ServerXYZ
IF (!($?)) {
$error[0] | export-clixml C:\myerror.xml
}
This solves my problem:
$Result = Install-WindowsFeature -Name SNMP-Service -IncludeAllSubFeature -IncludeManagementTools
Write-Host $Result.ExitCode