At my office (about 7000 computers) every PC has an IPv4 reservation for security measures.
If a computer is replaced the reservation needs to be cleaned, but there are multiple scopes that it could be in.
I've created a script which searches for the MAC address you provide, through every scope but also generates an error at every scope where that MAC address is not found.
The removing of the IP reservation works but what I want the script to do is the following:
First it should search the list of scopes for the correct scope which the computer is in, then it should execute the code in which it actually removes the reservation.
Also, I tried giving a text output for when the MAC adress is not found in any scope at all, but that doesn't seem to work either.
Here's my code:
Write-Host "remove mac-address"
$Mac = Read-Host "Mac-Adres"
$ScopeList = Get-Content sometxtfilewithscopes.txt
foreach($Scope in $Scopelist)
{
Remove-DhcpServerv4reservation -ComputerName #ipofdhcpserver# -ClientId $Mac -ScopeId $scope -erroraction SilentlyContinue -PassThru -Confirm -OutVariable NotFound | Out-Null
}
if ($NotFound -eq $false ) {
Write-Host "MAC-address not found!"
}
pause
Try something like this (this is what I use for something similar):
$mac = Read-Host "Enter MAC Address"
if ($mac -eq $null) { Write-Error "No MAC Address Supplied" -ErrorAction Stop }
$ServerName = "mydhcpserver.mydomain.net"
$ScopeList = Get-DhcpServerv4Scope -ComputerName $ServerName
ForEach ($dhcpScope in $ScopeList) {
Get-DhcpServerv4Reservation -ScopeId $dhcpScope.ScopeId -ComputerName $ServerName | `
Where {($_.ClientID -replace "-","").ToUpper() -eq $mac.ToUpper()} | `
ForEach {
Try {
Remove-DhcpServerv4Reservation -ClientId $_.ClientID -ScopeId $dhcpScope.ScopeId -Server $ServerName -WhatIf
} catch {
Write-Warning ("Error Removing From Scope" + $dhcpScope.ScopeId)
}
}
}
Just let PowerShell do all the heavy lifting for you:
$mac = Read-Host 'Enter MAC address'
$server = 'yourdhcpserver'
$reservation = Get-DhcpServerv4Scope -Computer $server |
Get-DhcpServerv4Reservation -Computer $server |
Where-Object { $_.ClientId -eq $mac }
if ($reservation) {
$reservation | Remove-DhcpServerv4Reservation -Computer $server
} else {
"$mac not found."
}
The above assumes that the entered MAC address has the form ##-##-##-##-##-##. If you want to allow colons as well (##:##:##:##:##:##) you need to replace the colons with hyphens before using the address in the Where-Object filter:
$mac = $mac -replace ':', '-'
Related
Problem:
I need to gather information from every workstation in my environment.
Details:
I am running a script from a machine which has rights on one Domain which makes it not require credentials in a script for about 80% of the workstations. However about 20% of the workstations are on other domains or no domain at all and require creds.
Solutions:
Does anyone have a clever way of handling creds like this? It seems that the local creds don't always work, not sure why. So I've resorted to trial and error. See the example.
Example:
So in this example I check using no credential, and if it doesn't return a result I try a credentials, then a different credential if need be. PS. Remoting is off and WinRM is also off on these machines.
$isApplied = "UNKNOWN"
$HotFix = $null
ForEach ($Item in $ComputerList) {
if ($null -eq $HotFix) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB #=> Naked
}
if ($null -eq $HotFix) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB -Credential $Cred1
}
if ($null -eq $HotFix) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB -Credential $Cred2
}
if ($null -eq $HotFix) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB -Credential $Cred3
}
if ($true -eq $HotFix) {
$isApplied = "APPLIED"
break
}
if ($false -eq $HotFix) {
$isApplied = "NOT_APPLIED"
break
}
}
If you don't want to run the command on each computer blindly, you can first query each domain to see if the computer is member of one of them. Lastly, if computer is not found in any domain, run the command for workgroup computer.
Something like this:
$isApplied = "UNKNOWN"
$HotFix = $null
$Domain1 = "1stDomain.com"
$Domain2 = "2ndDomain.com"
$Domain3 = "3rdDomain.com"
$FailedComputers = #() # See purpose below
$ErrorActionPreference = 'SilentlyContinue' # Optional: Don't display error message when Get-ADComputer doesn't find the computer (set globally for the script)
ForEach ($Item in $ComputerList) {
$HotFix = $null # reset value before processing each computer
if(Get-ADComputer -Identity $Item -Server $Domain1) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB -Credential $Cred1
} elseif(Get-ADComputer -Identity $Item -Server $Domain2) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB -Credential $Cred2
} elseif(Get-ADComputer -Identity $Item -Server $Domain3) {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB -Credential $Cred3
} else {
$HotFix = isHotFixInstalled -ComputerName $Item -KB $KB #=> Naked (for Workgroup Computers I suppose)
}
# BREAK below will only exit the foreach loop and stop processing computers, really the desired behavior?
#------------------------------------
#if ($true -eq $HotFix) {
# $isApplied = "APPLIED"
# break
#}
#if ($false -eq $HotFix) {
# $isApplied = "NOT_APPLIED"
# break
#}
#------------------------------------
# Instead, you can report failed computers by using a variable ($FailedComputers) and store the list in
if ($HotFix -eq $null) {
$FailedComputers += $Item
}
}
$ErrorActionPreference = 'Continue' # Optional: If changed before the loop execution, reset to default value
Write-Host "Hotfix Failed for the following computers"
$FailedComputers
FYI: I'm very new to PS and I'm using this as a learning opportunity. Again, I'm trying to find a
specific application on a list of multiple remote devices and determine the version number of the
application on their corresponding host system. I attempted this via a registry query (found this to
be challenging) and then I used Get-WMIObject. As of now, I'm working with this as my script. It's
not producing any output; instead, it returns to the command prompt with no errors or messages.
Script to find specific application and version in multiple remote devices:
$Servers = Get-Content -Path C:\\files\Serverlist.txt
$CIMSession = New-CIMSession -ComputerName $Servers Get-Credentials
$Vendor = "App Name"
foreach($Serv in $Servers) {
If(Test-Connection -ComputerName $Serv -Count 1 -Quiet) {
$Status = Get-Ciminstance Win32_Product -Computername $Serv | Where-object {$_.Version -contains
$Vendor}
if($Status) {
Out-file -Filepath C:\\files\AppVerResults.txt
}
}
}
I also tried adjusting the following section of the script as shown below but it presented me with the error "Get-CimInstance : Access is denied." Is this error message due to group policy or so? I am able to remote into the device corresponding to the message via RDP.
if($Status) {
$Servers + " - "
$Status | Out-file -Filepath C:\\files\AppVerResults.txt
}
}
}
Should I go about it via invoke-command or registry query? I'm slowly picking things up so I'll continue my research but I was hoping to get some advice in the meantime.
I still believe searching the registry is the easier way to go unless you have the specific file path for the .exe.
Use this function to find software on a remote, or local PC. Theres a filter option by specifying -SoftwareName (to look for).
Find-Software -ComputerName Remote_ComputerName -SoftwareName 'SQL'
Also accepts pipeline input, as well as multiple computer names to query for.
Find-Software -ComputerName ComputerOne, ComputerTwo, ComputerThree -SoftwareName 'SQL'
'ComputerOne','ComputerTwo' | Find-Software -SoftwareName 'SQL'
Exporting is also allowed by piping to an Export-* cmdlet.
Heres the code:
Function Find-Software {
[cmdletBinding()]
Param(
[Parameter(Mandatory=$false,
ValueFromPipeLine=$true,
ValueFromPipeLineByPropertyName=$true)]
[Alias('cn','name')]
[string[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory=$false)]
[String]$SoftwareName
)
Begin{
#Get Computer Names to check software version for
$Server_List = Get-Content -Path "C:\files\Serverlist.txt"
#Get Credentials for Script Scope once.
$Credentials = Get-Credential
}
Process{
if($PSBoundParameters.ContainsKey('SoftwareName')){
foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -Credential $Credentials -EnableNetworkAccess -ErrorAction Stop
$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" } -Session $PSSession
$Software_List = $Software_List | Where-Object -FilterScript {$_.DisplayName -match $SoftwareName} | Sort-Object -Property DisplayName
foreach($Software in $Software_List){
if($Software){
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = $Software.DisplayName
" Version " = $Software.DisplayVersion
}
} else {
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = "Not found"
" Version " = $null
}
}
}
} Catch {
"Unable to connect to PC: $Computer"
"Error: $($Error[0].Message.Split('.')[1].Trim())"
}
}
} else {
foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -Credential $Credentials -EnableNetworkAccess -ErrorAction Stop
$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" } -Session $PSSession
$Software_List = $Software_List | Sort-Object -Property DisplayName
foreach($Software in $Software_List){
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = $Software.DisplayName
" Version " = $Software.DisplayVersion
}
}
} Catch {
"Unable to connect to PC: $Computer"
"Error: $($Error[0].Message.Split('.')[1].Trim())"
}
}
} #end ELSE statement
} #end PROCESS block
End {
if(Get-PSSession){
Get-PSSession | Remove-PSSession
}
} #end END block - Perform Session Clean Up
} #end FUNCTION
Simply modify it to fit your needs :)
I'm trying to disable RDP using powershell.
I've tried the following code, but the values on the machine name I'm listing aren't changing.
$file = Get-Content c:\PSscripts\regchange\computers.txt
foreach ($computername in $file){
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$computername'" | Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computername )
$regKey= $reg.OpenSubKey("System\\CurrentControlSet\\Control\\Terminal Server" ,$true)
$regKey.SetValue("fDenyTSConnections","1",[Microsoft.Win32.RegistryValueKind]::dword)
}
else {
Write-Host "$computername unreachable"
}
}
I suspect there's something wrong with the way I entered the registry path name. any help would be appreciated.
The issue must be either permissions (which I assume you have as there are no obvious error messages), refreshing issue or in Get-Content and the structure of your file.
In order for Get-Content to work in this manner, each computer on a separate line. e.g.:
MyComputer1
MyComputer2
Another troubleshooting step is to try adding in Write-Host $computername entries to verify that you are looping through properly.:
$file = Get-Content c:\PSscripts\regchange\computers.txt
foreach ($computername in $file){
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$computername'" | Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
Write-Host "$computername set"
}
else {
Write-Host "$computername unreachable"
}
}
You can also confirm by adding in a $regKey.GetValue after setting:
$file = Get-Content c:\PSscripts\regchange\computers.txt
foreach ($computername in $file){
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$computername'" | Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computername )
$regKey= $reg.OpenSubKey("System\\CurrentControlSet\\Control\\Terminal Server" ,$true)
$regKey.SetValue("fDenyTSConnections","1",[Microsoft.Win32.RegistryValueKind]::dword)
Write-Host "$computername set to: $($regKey.GetValue("fDenyTSConnections"))"
}
else {
Write-Host "$computername unreachable"
}
}
Manually setting $computername = "MyComputer" and running the code, I can confirm that the code for setting the registry works... I can also confirm that remotely killing your RDP access to your remote virtual workstation also works.. and... is as terrible as it sounds ;-)
If PSRemoting is enabled, try something like this …
(This needs to be executed in a PowerShell elevated admin session.)
Get-Content -Path 'c:\PSscripts\regchange\computers.txt' |
ForEach{
If (Test-Connection -$PSItem -Count 1 -Quiet)
{
$paramblock = #{
Path = 'HKLM:\System\CurrentControlSet\Control\Terminal Server'
Name = 'fDenyTSConnections'
Value = '1'
}
Invoke-Command –Computername $PSItem –ScriptBlock {Set-ItemProperty #paramblock}
}
Else
{Write-Warning -Message "Either the host $PSItem is offline or not reachable."}
}
i want to shut down almost all PCs at my workplace (if they run more than 2 days)
I've worked the last and this week on a Script and trying to get rid of Errors on the way.
$days = -0
$date = (get-date).adddays($days)
$lastboot = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$Computer = Get-ADComputer -SearchBase 'OU=______,OU=______,DC=______,DC=______' ` -Filter '*' | Select -EXP Name
$lastbootconverted = ([WMI]'').ConvertToDateTime($lastboot)
write-host $date
write-host $lastboot
write-host $lastbootconverted
if($date -gt $lastbootconverted)
{
write-host Need to reboot
(Stop-Computer -$Computer -Force)
}
else
{
write-host no need to reboot
}
When I run it it says
"The RPC-Server isn't available. (Exception HRESULT: 0x800706BA)"
But if I just put a PC Name instead of the "$Computer", it shuts the PC down like I want. What is this RPC-Server Error? I don't have a firewall activated, so I'm clueless...
The OU=_____ and DC=______ are private company names
I've got not AD environment to test your Get-ADComputer query, but this worked for me with just an array of computer so should be fine for you.
function Get-LastBootUpTime {
param (
$ComputerName
)
$OperatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName
[Management.ManagementDateTimeConverter]::ToDateTime($OperatingSystem.LastBootUpTime)
}
$Days = -2
$ShutdownDate = (Get-Date).adddays($days)
$ComputerList = Get-ADComputer -SearchBase 'OU=______,OU=______,DC=______,DC=______' ` -Filter '*' | Select -EXP Name
$ComputerList | foreach {
$Bootup = Get-LastBootUpTime -ComputerName $_
Write-Host "$_ last booted: $Bootup"
if ($ShutdownDate -gt $Bootup) {
Write-Host "Rebooting Computer: $_" -ForegroundColor Red
Restart-Computer $_ -Force
}
else {
Write-Host "No need to reboot: $_" -ForegroundColor Green
}
}
The code below turns off firewall on each remote computers and return any computers that was turned off. I am also trying to retrieve software that has been authorized to pass through firewall for each computer.
I understand that I am using try, catch so is there any way to print the output of $Appfilter to offComp&programsALLO.txt ? The text file just prints the value of $Appfilter.
The output should ideally look like:
Computers:
"name of computer" followed by "programs allowed"
Here is the code:
Get-ADComputer -Filter * | Select-Object -ExpandProperty Name | Out-File .\ADcomputers.txt
$LaunchLine = 'powershell.exe -Version 4.0 -Command "& {netsh advfirewall set allprofiles state off}"'
$Appfilter = 'powershell.exe -Version 4.0 -Command "& {Get-NetFirewallApplicationFilter -program * | fl program}"'
$ComputerList = Get-Content .\adcomputers.txt
foreach($Computer in $ComputerList) {
[String]$wmiPath = "\\{0}\root\cimv2:win32_process" -f $computer
try {
[wmiclass]$Executor = $wmiPath
$executor.Create($LaunchLine, $Appfilter)
} catch {
Add-Content offComp&programsALLO.txt "computers:$Computer, $Appfilter "
}
}
I would use Invoke-Command with the -ComputerName parameter if possible:
#store AD Computer names in an array
$computerList = (Get-ADComputer -Filter *).Name
#declare results arrays
$results = #()
$offline = #()
#for each computer
foreach($computer in $computerList) {
#if computer responds to ping
if(Test-Connection $computer -Count 2 -Quiet -ErrorAction SilentlyContinue) {
#disable firewall
Invoke-Command -ComputerName $computer -ScriptBlock {
netsh advfirewall set allprofiles state off
} | Out-Null
#store retrieved authorized programs list in an array
$programs = Invoke-Command -ComputerName $computer -ScriptBlock {
(Get-NetFirewallApplicationFilter).Program
}
#build results object and add it to results array
$results += [PSCustomObject]#{
ComputerName = $computer
Programs = $programs -join ";"
}
} else {
#build results object and add it to offline array
$offline += [PSCustomObject]#{
ComputerName = $computer
Status = "OFFLINE"
}
}
}
#export results to files
$results | Out-File "report.txt"
$offline | Out-File "offline.txt"