Powershell ScriptBlock with 'cmd /c' and '$using' inconsistent behavior - powershell

My code:
Clear-Host
$installed_softwares = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty | Where-Object {$_.DisplayName -like "*" } | Select-Object -Property *
$csv = Import-Csv('C:\<...>\softwares.csv')
$softwares_to_remove = #()
foreach ($line in $csv) {
$info = New-Object System.Object
$info | Add-Member -type NoteProperty -name Name -value $line.Software
$info | Add-Member -type NoteProperty -name Version -value $line.Version
$info | Add-Member -type NoteProperty -name Publisher -value $line.Publisher
$softwares_to_remove += $info
}
ForEach ($installed_software in $installed_softwares) {
ForEach ($software_to_remove in $softwares_to_remove) {
If ($software_to_remove.Name -eq $installed_software.DisplayName -And $software_to_remove.Version -eq $installed_software.DisplayVersion -And $software_to_remove.Publisher -eq $installed_software.Publisher) {
Write-Host 'Tring to remove:' $installed_software.DisplayName, $installed_software.DisplayVersion, $installed_software.Publisher, $installed_software.UninstallString
If (($installed_software.UninstallString).ToLower() -match '^msiexec') {
Write-Host 'Skipped: ' $installed_software.DisplayName
Write-Host ''
} Else {
# Working: .\<SOFTWARE>\uninst.exe /S
$full_path = $installed_software.UninstallString
if ($full_path -like '* *') {
if ($full_path -notlike '"*') {
$full_path = '"' + $full_path + '"'
}
}
write-host $full_path, $full_path.GetType()
Invoke-Command -ComputerName localhost -ScriptBlock { cmd /c $using:full_path /S}
Write-Host 'Removed:' $installed_software.DisplayName
Write-Host ''
}
}
}
}
The output:
Tring to remove: BitComet 1.36 1.36 CometNetwork C:\Program Files (x86)\BitComet\uninst.exe
"C:\Program Files (x86)\BitComet\uninst.exe" System.String
Removed: BitComet 1.36
Tring to remove: qBittorrent 3.3.14 3.3.14 The qBittorrent project "C:\Program Files (x86)\qBittorrent\uninst.exe"
"C:\Program Files (x86)\qBittorrent\uninst.exe" System.String
Removed: qBittorrent 3.3.14
Tring to remove: Steam 2.10.91.91 Valve Corporation C:\Program Files (x86)\Steam\uninstall.exe
"C:\Program Files (x86)\Steam\uninstall.exe" System.String
Removed: Steam
Tring to remove: Viber 6.8.6.5 Viber Media Inc. MsiExec.exe /I{05247C1B-0AD7-43B0-B6F9-D29B376ADC9A}
Skipped: Viber
Currently, I'm running it locally via Powershell ISE started as Local Adminisrator, but the idea is to run this remotely when it's done. Sometimes the only software that gets uninstalled after I run this script many times is qBittorrent, but it doesn't do it flawlessly as it leaves the entry inside "Add/Remove Programs" in Control Panel, if it were a manual uninstall this wouldn't happen.
Why this code have this inconsistent behavior? How to fix it?

Related

Powershell Onedrive uninstall script, file not found

I'm trying to make a powershell script to uninstall Onedrive from users computers by running Invoke-Command -computername computer1 -filepath script.ps1
but i keep getting issues with the last step.
The first function "doUninstall" works just fine, it runs it and so on.
But the second one "douninstall2" doesnt work, it cant run the file
Anyone have any ideas what im doing wrong and how to fix this?
When i try running it using
Start-Process -filepath $UninstallCommand -ArgumentList "$EXEArgumente" -Wait
I can see the process starting on the target computer but it closes immediately
C:\Users\myuser\AppData\Local\Microsoft\OneDrive\23.002.0102.0004_5\OneDriveSetup.exe /uninstall
DEBUG: 78+ >>>> Write-Output "Uninstalling OneDrive found in Uninstall registry key"
Uninstalling OneDrive found in Uninstall registry key
DEBUG: 79+ >>>> $proc = Start-Process "$UninstallString" -PassThru
DEBUG: 83+ >>>> Write-Output "Uninstall failed with exception $_.exception.message"
Uninstall failed with exception This command cannot be run due to the error: The system cannot find the file specified..exception.message
#Requires -RunAsAdministrator
Set-PSDebug -Trace 2
function doUninstall($check) {
if (Test-Path $check) {
Write-Output "Uninstalling OneDrive found in $check"
$proc = Start-Process $check "/uninstall" -PassThru
$proc.WaitForExit()
}
}
function douninstall2 {
if (Test-Path $UninstallString) {
$UninstallString
Write-Output "Uninstalling OneDrive found in Uninstall registry key"
$proc = Start-Process "$UninstallString" -PassThru
$proc.WaitForExit()
}
else {
write-output "File not found"
}
}
function unstring {
$PatternSID = 'S-1-5-21-\d+-\d+\-\d+\-\d+$'
$username = Read-Host "Enter User ID: "
$ApplicationName = "Microsoft Onedrive"
# Get Username, SID, and location of ntuser.dat for all users
$ProfileList = gp 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*' | Where-Object {$_.PSChildName -match $PatternSID} |
Select #{name="SID";expression={$_.PSChildName}},
#{name="UserHive";expression={"$($_.ProfileImagePath)\ntuser.dat"}},
#{name="$username";expression={$_.ProfileImagePath -replace '^(.*[\\\/])', ''}}
# Get all user SIDs found in HKEY_USERS (ntuder.dat files that are loaded)
$LoadedHives = gci Registry::HKEY_USERS | ? {$_.PSChildname -match $PatternSID} | Select #{name="SID";expression={$_.PSChildName}}
# Get all users that are not currently logged
$UnloadedHives = Compare-Object $ProfileList.SID $LoadedHives.SID | Select #{name="SID";expression={$_.InputObject}}, UserHive, Username
# Loop through each profile on the machine
Foreach ($item in $ProfileList) {
# Load User ntuser.dat if it's not already loaded
IF ($item.SID -in $UnloadedHives.SID) {
reg load HKU\$($Item.SID) $($Item.UserHive) | Out-Null
}
#####################################################################
# This is where you can read/modify a users portion of the registry
# This example lists the Uninstall keys for each user registry hive
"{0}" -f $($item.Username) | Write-Output
$Global:Selection = (Get-ChildItem -Path registry::HKEY_USERS\$($Item.SID)\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_.DisplayName -match "$ApplicationName" } | Select-Object Publisher,DisplayName,Version,UninstallString)
#$Selection2 = (Get-ChildItem -Path registry::HKEY_USERS\$($Item.SID)\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_.DisplayName -match "$ApplicationName" } | Select-Object Publisher,DisplayName,Version,UninstallString)
#####################################################################
$Selection | ForEach-Object {
$Global:UninstallString = $_.UninstallString
}
}
# Unload ntuser.dat
IF ($item.SID -in $UnloadedHives.SID) {
### Garbage collection and closing of ntuser.dat ###
[gc]::Collect()
reg unload HKU\$($Item.SID) | Out-Null
}
}
try {
$check1 = "$ENV:SystemRoot\System32\OneDriveSetup.exe"
$check2 = "$ENV:SystemRoot\SysWOW64\OneDriveSetup.exe"
$check3 = "$ENV:ProgramFiles\Microsoft Office\root\Integration\Addons\OneDriveSetup.exe"
$check4 = "${ENV:ProgramFiles(x86)}\Microsoft Office\root\Integration\Addons\OneDriveSetup.exe"
Write-Output "Stopping OneDrive processes..."
Stop-Process -Name OneDrive* -Force -ErrorAction SilentlyContinue
# Uninstall from common locations
doUninstall($check1)
doUninstall($check2)
doUninstall($check3)
doUninstall($check4)
# Uninstall from Uninstall registry key UninstallString
unstring
douninstall2
}
catch {
Write-Output "Uninstall failed with exception $_.exception.message"
exit 1
}
I've tried multiple solutions, like adding "" around the path, adding in invoke-command inside the code but nothing works for me.
uninstall-package wont work for me here.

Replace second from last grouping of the MAC address in a csv file

I am struggling for a couple of days to manipulate the output of a script, that generates the full IPtable of a vm, for the purpose of restoring set settings once the host is upgraded. Bellow is the .ps1 file:
$sudatapath = "c:\UpgradeData"
$OSV = [environment]::OSVersion.Version
$psVersion = $PSVersionTable.PSVersion
If ($psVersion) {
$psversion=2
}
Else{
$psversion=1
}
write-host "vmretainer ran" >$sudatapath\vmretainerran.txt
#Check if task needs scheduling
if( -not (test-path "$sudatapath\iprestoredfinal.flg") -and ($psversion -eq 2))
{
write-host $psversion
schtasks /create /tn "vmipretainer" /sc onstart /rl highest /ru system /tr "powershell.exe -Executionpolicy bypass -NonInteractive -NoLogo -NoProfile -File c:\UpgradeData\vmipretainer.ps1" /F
}
if( -not (test-path "$sudatapath\iprestoredfinal.flg") -and ($psversion -eq 1))
{
write-host $psversion
schtasks /create /tn "vmipretainer" /sc onstart /rl highest /ru system /tr "powershell.exe c:\UpgradeData\vmipretainer.ps1 -noprofile –Noninteractive" /F
}
if( -not (test-path "$sudatapath\ipexported.flg"))
{
if( -not (test-path "$sudatapath")) {mkdir $sudatapath}
write-host "ipexport ran" >$sudatapath\ipexportran.txt
if ($psversion -eq 2)
{
$ResultIPTable = #()
$NICs = Get-WMIObject Win32_NetworkAdapterConfiguration -computername . | where{$_.IPEnabled -eq $true}
Foreach($NIC in $NICs) {
$IPTable = #{}
$IPTable.Add("sIP",$NIC.IPAddress[0])
$IPTable.Add("sMACAddress",$NIC.MACAddress)
$IPTable.Add("sgateway",[string]$NIC.DefaultIPGateway)
$IPTable.Add("smask",$NIC.IPSubnet[0])
$IPTable.Add("sdns",[string]$NIC.DNSServerSearchOrder)
$IPTable.Add("sdescription",$NIC.Description)
$IPTable.Add("sDHCPEnabled",$NIC.DHCPEnabled)
$IPTable.Add("sDNSDomain",$NIC.DNSDomain)
$ResultIPTable += New-Object PsObject -Property $IPTable
}
$ResultIPTable | Export-Csv -NoTypeInformation "$sudatapath\ipsettings.csv"
#$ResultIPTable | format-table -property *
$gd = Get-Date
echo "exportedsettings" $gd >"$sudatapath\ipexported.flg"
exit
}
if ($psversion -eq 1)
{
$result2008table = #()
$NICs = Get-WMIObject Win32_NetworkAdapterConfiguration -computername . | where{$_.IPEnabled -eq $true}
Foreach($NIC in $NICs) {
$2008table = #{}
$2008table = New-Object -TypeName PSObject
$2008table | Add-Member -MemberType NoteProperty -Name sIP -Value $NIC.IPAddress[0]
$2008table | Add-Member -MemberType NoteProperty -Name sMACAddress -Value $NIC.MACAddress
$2008table | Add-Member -MemberType NoteProperty -Name sgateway -Value ([string]$NIC.DefaultIPGateway)
$2008table | Add-Member -MemberType NoteProperty -Name smask -Value $NIC.IPSubnet[0]
$2008table | Add-Member -MemberType NoteProperty -Name sdns -Value ([string]$NIC.DNSServerSearchOrder)
$2008table | Add-Member -MemberType NoteProperty -Name sdescription -Value $NIC.Description
$2008table | Add-Member -MemberType NoteProperty -Name sDHCPEnabled -Value $NIC.DHCPEnabled
$2008table | Add-Member -MemberType NoteProperty -Name sDNSDomain -Value $NIC.DNSDomain
$Result2008Table += $2008table
$Result2008Table | format-table -property *
$Result2008Table | Export-Csv -NoTypeInformation "$sudatapath\ipsettings.csv"
$gd = Get-Date
echo "exportedsettings" $gd >"$sudatapath\ipexported.flg"
}
}
}
if (test-path "$sudatapath\ipexported.flg")
{
write-host "Restoring NIC IP"
write-host "Restoring NIC IP" >$sudatapath\iprestoreran.txt
$NICIMPORTS = Import-Csv "$sudatapath\ipsettings.csv"
$i = 0
$NICs = Get-WMIObject Win32_NetworkAdapterConfiguration -computername . | where{$_.IPEnabled -eq $true}
foreach ($NICIMPORT in $NICIMPORTS){
$sIP = $NICIMPORT.sIP
$sMACAddress = $NICIMPORT.sMACAddress
$sgateway = $NICIMPORT.sgateway.trim('{}')
$smask = $NICIMPORT.smask
$sdns = $NICIMPORT.sdns.trim('{}')
# $sdescription = $NICIMPORT.sdescription
$sDHCPEnabled = $NICIMPORT.sDHCPEnabled
# $sDNSDomain = $NICIMPORT.sDNSDomain
if( $NICs[$i].IPAddress[0] -ne $sIP)
{
write-host "IPs do not match $NICs.IPAddress[0] $sIP"
write-host "IPs do not match $NICs.IPAddress[0] $sIP" >$sudatapath\ipchangeran.txt
$sndns=($sdns.Split(" ")[0],$sdns.Split(" ")[1])
write-host "Restoring IP Address from CSV"
$NICs[$i].EnableStatic($sIP, $smask)
$NICs[$i].SetGateways($sgateway)
$NICs[$i].SetDNSServerSearchOrder($sndns)
$gd = Get-Date
}
$i++
}
echo "IPrestored" $gd > "$sudatapath\iprestored.flg"
if (Get-item "$sudatapath\ipexported.flg" | Where LastWriteTime -lt (Get-Date).AddDays(-60)){schtasks.exe /delete /f /tn vmipretainer}
}
I tried numerous attempts, all have failed.
Here is one of them:
$sudatapath = "c:\UpgradeData"
$CSV = Import-CSV Import-Csv "$sudatapath\ipsettings.csv"
$newCsv = foreach ($row in $CSV) {
$row | Select-Object sMAcadress, #{Expression = {$row.sMACAddress.remove($13, $14).insert($13, '99')}}
$newCsv | Export-Csv -NoTypeInformation '$sudatapath\ipsettings2.csv'
}
I am looking for somethin inside the original script that does the thing that I want, at the moment the csv file looks like this:
"sdns","sMACAddress","sDNSDomain","sdescription","sDHCPEnabled","smask","sIP","sgateway"
"","00:15:5D:73:48:00",,"Microsoft Hyper-V Network Adapter","False","255.255.255.192","10.52.115.31","10.52.115.62"
"","00:15:5D:73:48:01",,"Microsoft Hyper-V Network Adapter #2","False","255.255.255.192","192.168.10.31",""
I want to change the value in column sMACAddress like this:
aa:bb:cc:dd:ee:ff to aa:bb:cc:dd:99:ff wihtout knowing the stored value of the column (macaddresses are dynamic, this is done for a match after the upgrade, that is why I need a specific value for the second to last pair, for the host I managed to figured it out, it is this script that is driving me crazy)
This made me crazy for now and I am trying to ask others if there is any solution to this.
Thank you for your input and have a great day.
If all you're looking for is the replacement of the 5th octet in the MAC Address for every row, one option is to use RegEx from this inside of a calculated property:
#"
"sdns","sMACAddress","sDNSDomain","sdescription","sDHCPEnabled","smask","sIP","sgateway"
"","00:15:5D:73:48:00",,"Microsoft Hyper-V Network Adapter","False","255.255.255.192","10.52.115.31","10.52.115.62"
"","00:15:5D:73:48:01",,"Microsoft Hyper-V Network Adapter #2","False","255.255.255.192","192.168.10.31",""
"# | ConvertFrom-Csv |
Select-Object -ExcludeProperty 'sMACAddress' -Property *,
#{
Name = 'sMACAddress';
Expression = {
$_.sMACAddress -replace "..(?=:..$)",'99'
}
}
This should give you results of:
sMACAddress
-----------
00:15:5D:73:99:00
00:15:5D:73:99:01
Here's a RegEx Demo that explains the pattern matching.

How to get details of OS name, .net framework details for multiple servers using powershell?

I am trying to get details of OS Name and .net framework details for multiple servers using PowerShell script below.
$servers = Get-Content -Path "C:\Users\vinay\Desktop\servers.txt"
Foreach ($s in $servers)
{
write-host $s
$s.PSDrive
$s.PSChildName
Add-Content C:\Users\vinay\Desktop\specs.txt "Specs:"
$OS = (Get-WmiObject Win32_OperatingSystem).CSName
Add-Content C:\Users\vinay\Desktop\specs.txt "`nOS:$OS"
$Bit = (Get-WMIObject win32_operatingsystem).name
Add-Content C:\Users\vinay\Desktop\specs.txt "`nOS Bit: $Bit"
$name = (Get-WmiObject Win32_OperatingSystem).OSArchitecture
Add-Content C:\Users\vinay\Desktop\specs.txt "`nServer Name: $name"
$dotnet = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | Get-ItemProperty -Name version -EA 0 | Where { $_.PSChildName -Match '^(?!S)\p{L}'} | Select PSChildName, version
Add-Content C:\Users\vinay\Desktop\specs.txt "`n.NET VERSION $dotnet"
$release = (Get-ItemProperty "HKLM:SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release
Add-Content C:\Users\vinay\Desktop\specs.txt "`nRelease number: $release"
Add-Content C:\Users\vinay\Desktop\specs.txt "`n----------------------------"
}
But i am getting details of the server in which i am running the script but not for other servers in the text file.
however write-host $s reads all the servers in the text file. Please help where i am doing wrong.
Continuing from my comment, you need to perform your code looping over the servers in your list and have that code run on that server instead of your own machine you are running the script from.
Also, I would have the code output objects instead of trying to add lines to a text file, so that you ca save the results in a structured format like CSV.
Try
$servers = Get-Content -Path "C:\Users\vinay\Desktop\servers.txt"
$specs = foreach ($s in $servers) {
if (Test-Connection -ComputerName $s -Count 1 -Quiet) {
Write-Host "Probing server $s"
# you may need to add parameter -Credential and supply the credentials
# of someone with administrative permissions on the server
Invoke-Command -ComputerName $s -ScriptBlock {
$os = (Get-CimInstance -ClassName Win32_OperatingSystem)
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
Get-ItemProperty -Name Version, Release -Erroraction SilentlyContinue |
Where-Object { $_.PSChildName -Match '^(?!S)\p{L}'} |
ForEach-Object {
[PsCustomObject]#{
'ComputerName' = $os.CSName
'Operating System' = $os.Caption
'Architecture' = $os.OSArchitecture
'Net Version' = [version]$_.Version
'Net Framework' = $_.PsChildName
'Net Release' = $_.Release
}
}
}
}
else {
Write-Warning "Computer '$s' cannot be reached.."
}
}
# remove extra properties PowerShell added
$specs = $specs | Select-Object * -ExcludeProperty PS*, RunspaceId
# output on screen
$specs | Format-Table -AutoSize
# output to CSV file you can open in Excel
$specs | Export-Csv -Path 'C:\Users\vinay\Desktop\specs.csv' -UseCulture -NoTypeInformation

Powershell - looping through an array

I'm looking to search the C and E drives of all Windows servers in
Active Directory for any existing copies of putty.exe and their version.
The output needs to have the server name, full path to the executable,
and the file version. So far I have the following code (which right now is only using
two servers for testing:
$ComputerName = Get-ADComputer -filter "name -like 'computer01' -or name `
-like 'server01'" | select -ExpandProperty name
$OutputArr = #()
$findFiles = foreach($computer in $computername){
$file = Invoke-Command -computername $computer { Get-ChildItem -Path `
c:\, e:\ -Recurse | where-object{(!$_.psiscontainer -eq $true) -and `
($_.name -like "putty.exe")} | ForEach-Object -process {$_.fullname} }
$output = $OutputObj = New-Object -TypeName PSobject
$OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $computer
$OutputObj | Add-Member -MemberType NoteProperty -Name FilePath -Value $file
$OutputArr += $OutputObj
Write-Verbose $OutputObj
}
$OutputArr | fl
The above code outputs the following array:
ComputerName : COMPUTER01
FilePath : {C:\Program Files\PuTTY\putty.exe, C:\Program Files (x86)\PuTTY\PUTTY.EXE}
ComputerName : SERVER01
FilePath : {C:\Program Files (x86)\putty\putty.exe, C:\Users\testuser\Desktop\Public Desktop\putty.exe}
This produces the correct data, but now I need to run another snippet of code against each
separate filepath under computername, but am not sure how to accomplish this, as it is
taking the full filepath with multiple entries.
Essentially, I need to separate each ComputerName in the array into multiple lines:
COMPUTER01,C:\Program Files\PuTTY\putty.exe
COMPUTER01,C:\Program Files (x86)\PuTTY\PUTTY.EXE
SERVER01,C:\Program Files (x86)\putty\putty.exe
Etc...
Is an array not the correct way to do it?
If you are working strictly with what you already have stored in $OutputArr, the following will work:
$out = foreach ($line in $OutputArr) {
if ($line.filepath.count -gt 1) {
foreach ($fp in $line.FilePath) {
[pscustomobject][ordered]#{ComputerName = $line.ComputerName; FilePath = $fp}
}
}
else {
$line
}
}
$out | ConvertTo-Csv -NoTypeInformation
The foreach loop creates new objects with properties ComputerName and FilePath and stores them in $out as an array of objects.
If you do not care about properties and just want a comma-delimited list, you can use the following:
foreach ($line in $OutputArr) {
if ($line.filepath.count -gt 1) {
foreach ($fp in $line.FilePath) {
"{0},{1}" -f $line.ComputerName,$fp
}
}
else {
"{0},{1}" -f $line.ComputerName,$line.FilePath
}
}
This does the same looping as the first solution but instead uses the format operator (-f) to format the output. Piping to ConvertTo-Csv formats the output to be comma-delimited with the properties as headers.
You could move your desired functionality into your code before you even store anything in $OutputArr. I feel like doing all this after all of the other looping to create $OutputArr is just adding inefficiency.
PowerShell can get tricky when doing remote sessions. The below script should be a good starting point for you. Here are some other areas for improvement:
Doing Get-ChildItem -Recurse at the root of a drive will use an inordinate amount of memory and you could cause unintentional page file expansion or even make a server unresponsive due to 100% memory usage. In my snippet below I am using a list of well known paths. If you need to to identify if putty.exe is started on additional machines, your monitoring solution hopefully has process performance data and you can search for putty.exe there.
Speaking of memory management, remote shells have limitations of how much memory they can use. If you run winrm get winrm/config/winrs you will see the upper limit.
If you are going to authenticate to additional resources from within your remote script blocks, you will need to set up authentication that supports double hop scenarios (CredSSP or Kerberos)
$computerNames = #('computer1','computer2')
foreach($computer in $computerNames)
{
<#
First Script Block checks well known paths for putty.exe
#>
$puttyResults = Invoke-Command -ComputerName $computer -ScriptBlock {
$wellKnownPaths = #()
$wellKnownPaths += Join-Path $env:USERPROFILE -ChildPath "Desktop"
$wellKnownPaths += "D:\tools\"
$wellKnownPaths += $env:Path.Split(';')
$puttyPaths = #()
foreach($path in $wellKnownPaths)
{
$puttyPaths += Get-ChildItem $path -Filter "putty.exe" -Recurse
}
if($puttyPaths.Count -gt 0)
{
$resultsArray = #()
foreach($path in $puttyPaths)
{
$resultsArray += [PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
PuttyPath = $path.FullName
}
}
return $resultsArray
}
return $null
}
if($puttyResults -ne $null)
{
foreach($result in $puttyResults)
{
<#
Second script block takes action against putty.exe
#>
$puttyExists = Invoke-Command -ComputerName $computer -ArgumentList #($result.PuttyPath) -ScriptBlock {
Param(
$PuttyPath
)
return (Test-Path $PuttyPath)
}
if($puttyExists)
{
$msg = "Putty exists on '{0}', at '{1}'" -f $result.ComputerName, $result.PuttyPath
Write-Host $msg -ForegroundColor:Yellow
}
}
}
}
I am not sure what exactly you are wanting to do, but this should work for iterating through your custom object. Your Invoke-Command can be simplified also.
$file = Invoke-Command -computername $computer { Get-ChildItem -Path "C:\", "E:\" -Recurse -File -Filter "putty.exe" | Select -Property VersionInfo }
$OutputObj = New-Object -TypeName PSobject
$OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $env:COMPUTERNAME
$OutputObj | Add-Member -MemberType NoteProperty -Name FilePath -Value $file
$OutputArr += $OutputObj
foreach ($item in $OutputArr)
{
for ($i = 0; $i -lt $item.FilePath.Count; $i++)
{
Write-Output ([string]::Join(', ', $item.ComputerName, $item.FilePath[$i].VersionInfo.FileName, $item.FilePath[$i].VersionInfo.FileVersion))
}
}

Function within a Function - Powershell

OK I am going to try to explain this as best as I can. What started out as a simple script has turned into a huge mess and now I cannot figure out how to get it working. I have been coming here for answers for some time so maybe you guys can help.
What I am trying to do is a import a list of systems and check to see if they are online. If they are online they go in one list and if not they go in another.
foreach ($server in $servers) {
if (Test-Connection $server -Count 1 -ea 0 -Quiet) {
Write-Host "$server Is Up" -ForegroundColor Green
$server | out-file -Append $liveSystems -ErrorAction SilentlyContinue
} else {
Write-Host "$server Is Down" -ForegroundColor Red
$server | out-file -Append $inactive -ErrorAction SilentlyContinue
}
}
From there I check to see if the application I need installed is on the systems. That is where things start to go off-track. When I run the function to process the $liveSystems file all I get is the last line of the file (or the same system over and over) and not each system as it should be.
function Is-Installed( $program ) {
$x86 = ((Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
$x64 = ((Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
}
$program
function process-file1 {
param($filename)
Get-Content $filename -PipelineVariable line | ForEach-Object {
Is-Installed -program "My_Service"
if (Is-Installed -eq "True") {
Write-Host "$server has agent installed" -ForegroundColor Green
$server | Out-File $installed -ErrorAction SilentlyContinue
}
else
{
Write-Host "$server does not have agent installed" -ForegroundColor Red
$server | Out-File -Append $notInstalled -ErrorAction SilentlyContinue
}
}
}
process-file1 -filename $liveSystems
Once I can get the systems to process through the list of installed and not installed I am trying to take the list of installed systems and check which ones have the service running and which ones do not.
$array = #()
foreach($i in (gc $installed)) {
$svc = Get-Service my_service -ComputerName $i -ea "0"
$obj = New-Object psobject -Property #{
Name = $svc.name
Status = $svc.status
Computer = $i
}
$array += $obj
}
$array | Select Computer,Name,Status | Export-Csv -Path $resultsFile -
NoTypeInformation
Last but not least I run through that list of running and not running and attempt to start the service on systems that are not running.
function process-CSVfile2 {
param($filename)
Import-Csv $filename |
ForEach-Object -PipelineVariable object {
if($_.Status -eq "Running") {
Write-Host "Your Service is currently Running on" $_.Computer
}
if($_.Status -eq "Stopped") {
$serviceName = 'my_service'
$service = Get-CimInstance Win32_Service -ComputerName $_.Computer -Filter "Name=$serviceName"
$service.Start()
$service.WaitForStatus("Started",'00:00:30')
Start-Sleep 10
}
}
}
Several of these blocks run separately but when put together they will not run. I can't seem to get past the second block where it just looks at the same line over and over.
In addition there is a piece I have been trying to get working that would install the application on systems that do not have the service installed but that is not working either but I will save that for a different time.
If anyone can help me with this I would really appreciate it. After 3 days of trying to get it running I am at my wits end.
I'd create objects and properties instead of files with computers online etc...
Something like:
$Computers=New-Object -TypeName System.Collections.ArrayList
$Servers = #(Get-Content -path c:\servers.txt)
$Servers = $Servers | ? {$_} | select-object -uniqe |ForEach-Object {$_.TrimEnd()}
$Servers|ForEach-Object {
$tempobj=New-Object -TypeName PSObject
$tempobj | Add-Member -type NoteProperty -name Name -value $_
$tempobj | Add-Member -type NoteProperty -name isOnline -value $FALSE
$tempobj | Add-Member -type NoteProperty -name Installed -value $FALSE
$tempobj | Add-Member -type NoteProperty -name serviceRunning -value $FALSE
[void]$Computers.Add($tempobj)
then You could work on array (no need for additional files)
$Computers|Where-Object {$_.isOnline -eq $TRUE}
etc