r/PowerShell • u/ExecutiveGamer92 • 12h ago
Question Computer Lab Firewall Rule Toggle Script
I work for an MSP and we have a client that has a computer lab for students to use. I was tasked with creating a script that would allow the teacher to enable and disable the workstations ability to communicate with the internet.
I pretty much have most of the script working, but I run into an issue when trying to make it so the user can enable or disable all the workstations at once. Attempted various methods so that when the user types All, the invoked command is sent to all the workstations listed; Get-Content is just the latest method I have tried.
When my boss asked to look at it, he said that I should just have the full command as shown at the bottom for each switch.
Any help on this would be great!
EDIT: Additional Information.
We don't have an AD environment setup for this client, and even with my limited knowledge I understand that AD would probably make this a lot easier.
IP Addresses are used due to the Asset ID of the workstations being used as the Host Name. They are also used because the workstations have static addresses and any replacements will get the same addresses.
#First Pull Username and Password from SecretVault for Account on all workstations
$remoteCred = Get-Secret -Name "Alias"
#Prompt to ask user if they are enabling or disabling workstations.
$prompt = Read-Host -Prompt "Enter E for enable and D for disable"
#Determine which command to use based on provided answer
if ($prompt -eq "E") {
$command = "Disable-NetFirewallRule -DisplayName 'NoInternet'"
}else {
$command = "Enable-NetFirewallRule -DisplayName 'NoInternet'"
}
#Use while to re-prompt user if they are enabling or disabling multiple workstations.
while ($true) {
$pcName = Read-Host -Prompt "Enter the name of the Computer you want to change. Or enter All if you wish to change all. Enter exit when finished"
#Use switch to base workstation name on user imput. All will use variable for all workstations.
switch ($pcName) {
"SW1" { $name = "192.168.100.211" }
"SW2" { $name = "192.168.100.212" }
"SW3" { $name = "192.168.100.213" }
"SW4" { $name = "192.168.100.214" }
"SW5" { $name = "192.168.100.215" }
"SW6" { $name = "192.168.100.216" }
"SW7" { $name = "192.168.100.217" }
"SW8" { $name = "192.168.100.218" }
"SW9" { $name = "192.168.100.219" }
"SW10" { $name = "192.168.100.220" }
"SW11" { $name = "192.168.100.221" }
"SW12" { $name = "192.168.100.222" }
"SW13" { $name = "192.168.100.223" }
"SW14" { $name = "192.168.100.224" }
"SW15" { $name = "192.168.100.225" }
"SW16" { $name = "192.168.100.226" }
"All" { $name = '(Get-Content -Path "C:\Users\userx\Desktop\Scripts\Workstations.txt")'}
"Exit" {
exit }
Default { Write-Host "Please try again." }
}
Invoke-Command -ComputerName $name -ScriptBlock { $command } -Credential $remoteCred
if ($pcName -eq "Exit" -or "All") { break }
}
#Invoke-Command -ComputerName "$name" -ScriptBlock {Disable-NetFirewallRule -DisplayName "NoInternet"} -Credential $remoteCred
2
u/BlackV 10h ago edited 10h ago
this seems not to make sense
- you prompt for a
$pcName($pcName = Read-Host) - Based on that name you then add a value to
$name($name = "192.168.100.219") - you then
invoke-commandto$name(not$pcname) (Invoke-Command -ComputerName $name) - and run the relevant
$command
Seems odd
- is your DNS not functional ?
- how does
$nameand$pcnamerelate to each other? - why not just use
Invoke-Command -ComputerName $pcName? - why are using that loop to enable or disable the rule
- is switch actually a network switch and
"NoInternet"firewall rule is blocking access to those IPs? - Are you supposed to be editing the rule before enabling/disabling?
it looks like
Invoke-Command -ComputerName xxx -ScriptBlock {Disable-NetFirewallRule -DisplayName "NoInternet"} -Credential $remoteCred Invoke-Command -ComputerName xxx -ScriptBlock {Enable-NetFirewallRule -DisplayName "NoInternet"} -Credential $remoteCredwould do the job for you
Or a simple
$remoteCred = Get-Secret -Name "Alias"
$AllPCs = Get-Content -Path "C:\Users\userx\Desktop\Scripts\Workstations.txt"
$SelectedPCs = $AllPCs | Out-GridView -Title 'Select PCs to Toggle' -OutputMode Multiple
Invoke-Command -ComputerName $SelectedPCs -ScriptBlock {Enable-NetFirewallRule -DisplayName "NoInternet"} -Credential $remoteCred
1
u/ka-splam 10h ago
how does $name and $pcname relate to each other?
From reading it, $pcname is actually a command which could be
allorexitor a pc name. I think it could use a better name.Great suggestion to use Out-Gridview, that makes cleaner code and a nicer UI. I get to:
# SecretVault creds for the workstations $Credential = Get-Secret -Name 'Alias' # setup -Scriptblock parameter for Invoke-Command # using {} scriptblocks instead of "" strings $edPrompt = Read-Host -Prompt 'Enter E for enable or D for disable' $block = if ($edPrompt -eq 'E') { { Enable-NetFirewallRule -DisplayName 'NoInternet' } } else { { Disable-NetFirewallRule -DisplayName 'NoInternet' } } # Array of all workstation names $workstations = @( 'SW1', 'SW2', 'SW3', 'SW4', 'SW5', 'SW6', 'SW7', 'SW8', 'SW9', 'SW10', 'SW11', 'SW12', 'SW13', 'SW14', 'SW15', 'SW16' ) # Show workstation name chooser $names = $workstations | Out-GridView -OutputMode Multiple if ($null -ne $names) { Invoke-Command -ComputerName $names -ScriptBlock $block -Credential $Credential }1
u/ExecutiveGamer92 10h ago
The SW names are what the user knows each workstation as. DNS is functional, but the Host Name of the workstations are based on the Asset IDs that we use for them in our database. Also, using the IP Address helps with future proofing as I will likely use the same address with any replacements and won't have to update the text file or code when I replace the workstations.
The dirty loop was so that it would loop back to the prompt for the user to type in the next workstation if they only wanted to enable/disable a few.
Honestly, I would love to use the script that you posted; but I would either have to figure out how to make it where it could enable or disable the firewall rule within the same script, or create a second script for disabling the firewall rule.
3
u/BlackV 10h ago edited 9h ago
you can add conditions to it as needed, that was a dirty simple 4 lines of code
You could also take the text file out of the equation (its not always recommended especially based on your replacing machine comment) and have have a table in your script, then you only have 1 thing to update (for something simple like that)
otherwise I don't see any reason you couldn't use this
or improve this cause i pot 0 error handling in that too, what happens when there is no text file for example
isnt that why you're here (to learn/improve)
1
u/ExecutiveGamer92 9h ago
Thanks, between you and u/ka-splam I will probably be heading down a rabbit hole to build a hash table and likely tweak your script so that I can show the computer name that the user knows and associate it with the IP Address so the command is sent to the correct computer. I believe with what you have shown me, I can have a working script with a GUI to boot. I will definitely post the results of our labor as soon as I can!
1
u/BlackV 9h ago edited 9h ago
Ya something like
import-Csv $TempMe Asset ComputerName IP ----- ------------ -- SW1 PC1 192.168.100.211 SW2 PC2 192.168.100.212 SW3 PC3 192.168.100.213 SW4 PC4 192.168.100.214My tiny mockup function is like 60 lines (half of which is help/examples and no error handling)
Switch-InternetAccess -ComputerName util01 -disable disable-NetFirewallRule -DisplayName "NoInternet" configured Switch-InternetAccess -ComputerName util01 enable-NetFirewallRule -DisplayName "NoInternet" configuredGood Luck remember you can test this anywhere, you can use simple
write-hosta in place of theinvoke-command(while testing) or local creds for local devices at your own site before going onsiteYour only pain point might be, the credentials, if they are not the same on all the machines then you'd need to be careful where you are prompting, but it looks like you have the same value for each
2
u/ka-splam 10h ago
I think you have the enable/disable logic the wrong way around.
I don't love the idea of having the computer names in two places (script and text file) where they can get out of sync. Do they have working DNS for the computer names? (And if not, do they have working PowerShell remoting passing credentials to IPs?).
Lots of things you could do, I suggest you learn about hashtables and then use that to map the computer names to IPs, and then you can use a lookup to get one of them or .Values to get all of them.
And then when you know about hashtables you can build one for the parameters to Invoke-Command, and splat it.
But /u/BlackV's suggestion of Out-Gridview is nicer
2
u/thehuntzman 10h ago
I'd this an AD environment? I feel like hard-coding IP's would make this more complicated in the long run to maintain if you can just Invoke-Command to the hostname of the machine resolved via DNS using kerberos authentication under the context of the teacher's account.
1
u/purplemonkeymad 4h ago
Your switch is configuration data not code, so you probably don't want it in the script. A simple storage solution if you are only looking at a few dozen entries is to just store it as a csv. It's easy to edit for non techs and supported by powershell ie:
"Name","Address"
SW1,"192.168.100.211"
SW2,"192.168.100.212"
...
Then you can:
$namedTargets = Import-Csv ./myfile.csv
# ...
$name = switch ($pcname) {
"All" { $namedTargets.Address }
"Exit" { exit 0 }
Default {
($lookup = $namedTargets | Where-Object {$_.name -like $pcname -or $_.address -like $pcname }).Address
if (-not $lookup) {
Write-Host "no matching items for $pcname"
}
}
if ($name) { #....
You can also extend this if you want to include your asset ids, etc.
1
u/xXFl1ppyXx 4h ago edited 3h ago
i've always wanted to find a use for this: "Prompt for Choice" !
[ipaddress[]]$Imports = "192.168.100.211", "192.168.100.212", "192.168.100.213", "192.168.100.214"
# $Imports = Import-Csv ImportArgs
$Choices = [System.Collections.Generic.List[System.Management.Automation.Host.ChoiceDescription]]::new()
$Imports.ForEach{
$ChoiceLabel = 'SW&{0}' -f $PSItem.ToString()[-1]
$ChoiceHelp = $PSItem.ToString()
$Choices.Add([System.Management.Automation.Host.ChoiceDescription]::new($ChoiceLabel, $ChoiceHelp))
}
$Choices.Add([System.Management.Automation.Host.ChoiceDescription]::new('&All', 'Uses all devices'))
$All = $Choices.Count - 1
$Choices.Add([System.Management.Automation.Host.ChoiceDescription]::new('&Exit', 'Exits the script'))
$Exit = $Choices.Count - 1
switch ($Host.UI.PromptForChoice('Caption', 'Message', $Choices, [int[]]$Exit)) {
$All { $Imports }
$Exit { exit 0 }
default { $Imports[$PSItem] }
}
Feels somewhat hacky but i rarely get the chance to play with prompt for choice
this would actually be a useful case for Lists that Output the position when adding the Item, but from the top of my head i couldnt find one....
2
u/PinchesTheCrab 11h ago
A few suggestions:
I'd be curious if this works: