I try to uninstall a msi file, but when I try this via array I get an error (cant find installation package)
When I do the same but not in array - it works
for ($i=0; $i -lt $msiArrayClean.length; $i++){
Write-Host $msiArrayClean[$i]
& msiexec.exe /x $msiArrayClean[$i]
}
here the output of Write Host
How i come to $msiArrayClean
$msiCache = get-wmiobject Win32_Product | Where-Object Name -like "*7-Zip*" | Format-Table LocalPackage -AutoSize -HideTableHeaders
$msiString = $msiCache | Out-String
$msiArrayWithEmptyLines = $msiString -split "`n"
$msiArray = $msiArrayWithEmptyLines.Split('', [System.StringSplitOptions]::RemoveEmptyEntries)
$msiArrayCleanString = $msiArray | Out-String
$msiArrayClean = $msiArrayCleanString -split "`n"
A few caveats up front:
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
The CIM cmdlets (e.g., Get-CimInstance) superseded the WMI cmdlets (e.g., Get-WmiObject) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (version 6 and above), where all future effort will go, doesn't even have them anymore. For more information, see this answer.
Use of the Win32_Product WMI class is discouraged, both for reasons of performance and due to potentially unwanted side effects - see this Microsoft article.
An alternative - available in Windows PowerShell only (not in PowerShell (Core) 7+) - is to use the following to get uninstall command lines and execute them via cmd /c:
Get-Package -ProviderName Programs -IncludeWindowsInstaller |
ForEach-Object { $_.meta.attributes['UninstallString'] }
If you need to stick with Win32_Product:
# Get the MSI package paths of all installed products, where defined.
$msiCache = (Get-CimInstance Win32_Product).LocalPackage -ne $null
foreach ($msiPackagePath in $msiCache) {
if (Test-Path -LiteralPath $msiPackagePath) {
# Note that msiexec.exe runs *asynchronously*.
# Use Start-Process -Wait to wait for each call to complete.
& msiexec.exe /x $msiPackagePath
} else {
Write-Warning "Package not found: $msiPackagePath"
}
}
I don't like reaching to WMI, since its perfomance is the issue. I prefer to do it via registry and it worked for me many times. Code explanation in comments.
$name = "7-zip"
#Get all items from registry
foreach ($obj in Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") {
#Get DisplayName property of registry
$dname = $obj.GetValue("DisplayName")
#Search for given name
if ($dname -like "*$name*") {
#Get uninstall string (it gets you msiexec /I{APPID})
$uninstString = $obj.GetValue("UninstallString")
foreach ($line in $uninstString) {
#Getting GUID from "{" to "}""
$found = $line -match '(\{.+\}).*'
if ($found) {
#If found - get GUID
$appid = $matches[1]
Write-Output "About to uninstall app $appid"
#Start uninstallation
Start-Process "msiexec.exe" -arg "/X $appid /qb" -Wait
}
}
}
}
Edit: Added solution with msi path after Nehat's comment as this works for me (I tried to minimize the code :))
$msiCache = get-wmiobject Win32_Product | Where-Object Name -like "*7-Zip*" | Format-Table LocalPackage -AutoSize -HideTableHeaders
foreach ($msi in $msiCache | Out-String) {
if ([string]::IsNullOrEmpty($msi)) {
continue
}
Write-Host $msi
Start-Process "msiexec.exe" -arg "/x $msi" -Wait
}
Related
$test = #(gwmi win32_networkadapterconfiguration | select macaddress )
$test | ForEach-Object {
Write-Host $_.macaddress
$mac = $_.macaddress -replace ":", ""
$mac.Trim()
If (Test-Path "x:\$Mac") { $computer = $mac }
$Logfile = "x:\$Computer\$Computer.Log"
$File = "x:\$computer\$computer.ini"
$computer
$CompName = Get-Content $File | Select-Object -index 0
}
So the above script will not find the $file even though it is present. The x:\64006A849B90\64006A849B90.ini is present but i get this
ERROR: Get-Content : Cannot find path 'X:\64006A849B90\64006A849B90.ini' because it does not exist.
Anyone know why i cant use this - i know its something to do with the $mac value and making sure its a string but i have tried $mac.ToString() [String]$mac and trimming it and it will not see the path - any ideas? thanks
The strange thing is the value is being picked up hence the mac address being in the path but it wont find the path if that makes sense.
I think you might have other issues but assuming your files are named and exist where you expect the only problem you would have to deal with is potential nulls.
Do you have any adapters that do no have MAC Addresses? I have 3 right now. Using your code it will attempt to process those. If you were not aware of those I could see that being an issue. Easy to fix will a small code update
# Get the populated macs from all network adapters
$macs = Get-WmiObject win32_networkadapterconfiguration | Select-Object -ExpandProperty macaddress
ForEach($mac in $macs){
$mac = $mac.replace(":","")
$macFile = "x:\$mac\$mac.ini"
if(Test-Path $macFile){
# The ini file exists
$computer = Get-Content $macFile | Select-Object -Index 0
} else {
# Cant find the file
}
$computer
}
This could be simplified even further but I didn't want to do too much at once.
By using Select-Object -ExpandProperty macaddress we still get nulls but they are dropped by the pipeline so $macs would only contain strings of actual MACs.
The whole $computer = $mac should have worked but it was redundant so I removed that logic from your code.
I'm having some difficulties in getting my PowerShell script to work as I'd like it to and after much jiggery-pokery here I am.
My overall aim is fairly simple, unfortunately I'm somewhat of a PowerShell noob!
I'm trying to determine the name, manufacturer and model of all of the systems in our estate without having to walk around staring at lots of tin.
I've constructed the following based solely on my bad knowledge of scripting and I've hit a snag.
My idea was to pass DNS/IP information from a CSV into a variable which I can then use in turn to perform the WMI query based on the Ping results.
False Ping response = do not query
True Ping response = perform WMI query
Here is what I've got so far...
Test-connection -computername
foreach ($Ping in $Hosts)
{
test-connection -computername $Ping.IP -count 1 -quiet
if ($Ping.StatusCode -eq 0)
{Get-WmiObject win32_computersystem -computername $ip.Name | select Name,Manufacturer,Model } out-file c:\CSV\Test1.csv -ea SilentlyContinue}
else
{write-host $Hosts.Status Offline}
}
Assuming you have file C:\CSV\Hosts.csv with contents as described:
computer1.mydomain.com
computer2.mydomain.com
With the following script you'll get file C:\CSV\Results.csv:
$Hosts = Get-Content "C:\CSV\Hosts.csv"
foreach ($PingIP in $Hosts)
{
$alive = Test-Connection -ComputerName "$PingIP" -count 1 -quiet
if ($alive)
{
Get-WmiObject Win32_ComputerSystem -ComputerName "$PingIP" | Select Name, Manufacturer, Model | Out-File "C:\CSV\Results.csv" -ea SilentlyContinue
}
else
{
Write-Output "$PingIP offline"
}
}
I am writing a batch file that executes a Powershell script that at one point loops items with UNC paths as attributes and uses Get-ChildItem on those paths. In a minimal version, this is what is happening in my scripts:
Master.bat
powershell -ExecutionPolicy ByPass -File "Slave.ps1"
Slave.ps1
$foo = #{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"
$bar = #{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"
#( $foo, $bar ) | ForEach-Object {
$item = Get-ChildItem $_.Path
# Do things with item
}
The problem I'm running into is that when I run Master.bat, it fails at Get-ChildItem with an error along the lines of
get-childitem : Cannot find path '\\remote-server\foothing' because it does not exist.
However, it seems to work perfectly fine if I run the Slave.ps1 file directly using Powershell. Why might this be happening only when the Master.bat file is run?
Things I have tried
Prepending the UNC paths with FileSystem:: with providers http://powershell.org/wp/2014/02/20/powershell-gotcha-unc-paths-and-providers/
Making sure there are no strange characters in the actual paths
Using the -literalPath parameter instead of the plain -path parameter for Get-ChildItem
Running Get-ChildItem \\remote-server\foothing in PowerShell and succeeding to verify connection to the remote server
I have found this issue when running scripts referring to UNC paths - but the error only occurs when the root of the script is set to a non file system location. e.g. PS SQLSEVER\
So the following fails with the same error:
cd env:
$foo = #{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"
$bar = #{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"
#( $foo, $bar ) | ForEach-Object {
$item = Get-ChildItem $_.Path
# Do things with item
Write-Host $item
}
So my resolution was to ensure that the PS prompt was returned to a file system location before executing this code. e.g.
cd env:
$foo = #{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"
$bar = #{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"
cd c: #THIS IS THE CRITICAL LINE
#( $foo, $bar ) | ForEach-Object {
$item = Get-ChildItem $_.Path
# Do things with item
Write-Host $item
}
I hope this helps - I would be very happy with the bounty as this is my first answer on stack overflow.
P.S. I forgot to add - the PS command prompt root may be set by auto loaded modules in the configuration of your machine. I would check with Get-Location to see if you are actually executng from a non FileSystem location.
Rory's answer provides an effective workaround, but there's a solution that doesn't require changing the current location to a FileSystem provider location first:
Prefix your UNC paths with FileSystem:: to ensure that they are recognized correctly, irrespective of the current location:
$foo = #{
Name = "Foo"
Path = "FileSystem::\\remote-server\foothing"
}
$bar = #{
Name = "Bar"
Path = "FileSystem::\\remote-server\barthing"
}
Alternatively, here is a tweak to Rory's answer to avoid changing the current location session-globally (to preserve whatever the current location is), using Push-Location and Pop-Location:
try {
# Switch to the *filesystem provider's* current location, whatever it is.
Push-Location (Get-Location -PSProvider FileSystem)
# Process the paths.
$foo, $bar | ForEach-Object {
$item = Get-ChildItem $_.Path
# Do things with item
}
} finally {
# Restore the previous location.
Pop-Location
}
Optional background information
This excellent blog post explains the underlying problem (emphasis added):
PowerShell doesn't recognize [UNC paths] as "rooted" because they're not on a PSDrive; as such, whatever provider is associated with PowerShell's current location will attempt to handle them.
Adding prefix FileSystem:: unambiguously identifies the path as being a FileSystem provider path, irrespective of the provider underlying the current location.
I read somewhere else about the Push-Location and Pop-Location commands to counter this kind of problem - I landed on your question while manually, step-by-step, testing a new routine where the script has push/pop, but I forgot to do them on my PS window. After checking #Rory's answer I noticed I was on PS SQLServer:\ instead of PS C:\ prompt.
So a way to use this on your "slave" script would be:
$foo = #{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"
$bar = #{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"
#( $foo, $bar ) | ForEach-Object {
$item = Get-ChildItem $_.Path
Push-Location
# Do things with item
Pop-Location
}
Thought of adding the Push/Pop before and after the # Do things because it seems that it's those things that change the location.
I am retrieving the hosts file on a server with 5 DNS entries:
C:\Windows\System32\drivers\etc\hosts
Mine looks like this after the comments:
127.0.0.1 infspcpd8tx8e.rtmphost.com
127.0.0.1 infspkbpef39p.rtmphost.com
127.0.0.1 infspo99vn3ti.rtmphost.com
127.0.0.1 infspqx6l10wu.rtmphost.com
127.0.0.1 infspvdkqjhkj.rtmphost.com
In my hosts file I see them as 5 lines on top of eachother, but when I paste it here it has a space inbetween. This is the same when I use get-content on that file, but I wouldn't expect that to stop me.
So I have an array that is gotten like so:
$ACCOUNTS = Get-ChildItem "D:\cyst\accounts\" | select name
I then try to see if there are duplicate entries in the hosts file by checking the $accounts variable against the array I got containing the hosts file.
foreach ($rtmp in $ACCOUNTS) {
$HostsFile = Get-Content C:\Windows\System32\drivers\etc\hosts | ForEach-Object {[System.Convert]::ToString($_)}
#$rt[string]$data = $HostsFile
[string]$rtmpfull = $rtmp.name + ".rtmphost.com"
if ($HostsFile -contains $rtmpfull) { Write-Host "Host found in hosts file moving on..." }
else { echo "wrong"
}
}
It never matches and always returns false, I can't match anything.. please help - is it a type issue? I've googled this for DAYS but now i'm desperate and posting here.
I think you can probably speed that up by just dispensing with the foreach.
(Get-Content C:\Windows\System32\drivers\etc\hosts) -match [regex]::escape($rtmpfull)
Should match the entire hosts file at once.
$ACCOUNTS = Get-ChildItem "D:\cyst\accounts\"
foreach ($rtmp in $ACCOUNTS){
$found=$FALSE
foreach ($line in (gc C:\Windows\System32\drivers\etc\hosts)){
if(($line -match $rtmp) -and ($found -eq $TRUE)){
echo "$($matches[0]) is a duplicate"
}
if (($line -match $rtmp) -and ($found -eq $FALSE)){
echo "Found $($matches[0]) in host file..."
$found=$TRUE
}
}
}
Not elegant, but it will do the job.
This test:
if ($HostsFile -contains $rtmpfull)
is looking for $rtmpfull to match an entire line stored in $HostsFile. You want to check for a partial match like so;
if ($HostsFile | Foreach {$_ -match $rtmpfull})
BTW you can simplify this:
$HostsFile = Get-Content C:\Windows\System32\drivers\etc\hosts | ForEach-Object {[System.Convert]::ToString($_)}
to:
$HostsFile = Get-Content C:\Windows\System32\drivers\etc\hosts
By default, Get-Content will give you an array of strings where each element of the array corresponds to a line in the file.
Full Question: Have Powershell Script using Invoke SQL command, using snappins, I need them to be included in a SQL job, the SQL Server version of Powershell is somewhat crippled, does anyone know a workaround?
From what I have gathered, SQL Management Studio's version of powershell is underpowered, not allowing for the use of snappins, as such it does not recognize the cmdlets that I used in the script. I have tried running it in the job as a command line prompt rather than a Powershell script, which causes the code to work somewhat, however I check the history on the job and it says that invoke-sql is still not a recognized cmdlet. I speculate that because I am running the code on a remote server, with different credentials than my standard my profile with the snappins preloaded isn't being loaded, though this is somewhat doubtful.
Also, as I am a powershell rookie, any advice on better coding practices/streamlining my code would be much appreciated!
Code is as follows:
# define parameters
param
(
$file = "\\server\folder\file.ps1"
)
"invoke-sqlcmd -query """ | out-file "\\server\folder\file.ps1"
# retrieve set of table objects
$path = invoke-sqlcmd -query "select TableName from table WITH (NoLock)" -database db -server server
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$so = New-Object Microsoft.SqlServer.Management.Smo.ScriptingOptions
$so.DriPrimaryKey = $false
$so.Nocollation = $true
$so.IncludeIfNotExists = $true
$so.NoIdentities = $true
$so.AnsiPadding = $false
# script each table
foreach ($table in $path)
{
#$holder = $table
$table = get-item sqlserver:\sql\server\default\databases\database\tables\dbo.$($table.TableName)
$table.script($so) | out-file -append $file
}
(get-content "\\server\folder\file.ps1") -notmatch "ANSI_NULLS" | out-file "\\server\folder\file.ps1"
(get-content "\\server\folder\file.ps1") -notmatch " AS "| out-file "\\server\folder\file.ps1"
(get-content "\\server\folder\file.ps1") -notmatch "Quoted_" | out-file "\\server\folder\file.ps1"
(get-content "\\server\folder\file.ps1") -replace "\) ON \[PRIMARY\].*", ")" | out-file "\\server\folder\file.ps1"
(get-content "\\server\folder\file.ps1") -replace "\[text\]", "[nvarchar](max)" | out-file "\\server\folder\file.ps1"
(get-content "\\server\folder\file.ps1") -replace " SPARSE ", "" | out-file "\\server\folder\file.ps1"
(get-content "\\server\folder\file.ps1") -replace "COLUMN_SET FOR ALL_SPARSE_COLUMNS", "" | out-file "\\server\folder\file.ps1"
""" -database database -server server" | out-file "\\server\folder\file.ps1" -append
So I figured out the answer to my own question. Using this site: http://www.mssqltips.com/tip.asp?tip=1684 and
http://www.mssqltips.com/tip.asp?tip=1199
I figured out that he was able to do so using a SQL Server Agent Proxy, so I followed the yellow brick road, and basically I set up a proxy to my account and was able to use the external powershell through a feature. A note, you need to create a credential under the securities tab in object explorer prior to being able to select one when creating the proxy. Basically I ended up creating a proxy named powershell, using the powershell subsystem, and use my login info to create a credential. VOILA!
You have to add the snapins each time. In your editor you likely already have them loaded from another script/tab/session. In SQL Server you will need to add something like this to the beginning of the script:
IF ( (Get-PSSnapin -Name sqlserverprovidersnapin100 -ErrorAction SilentlyContinue) -eq $null )
{
Add-PsSnapin sqlserverprovidersnapin100
}
IF ( (Get-PSSnapin -Name sqlservercmdletsnapin100 -ErrorAction SilentlyContinue) -eq $null )
{
Add-PsSnapin sqlservercmdletsnapin100
}
I'm not sure the error you are trying to workaround - can you post that?
Have you tried this from a PowerShell prompt?
Add-PSSnapin SqlServerCmdletSnapin100