设想
为了简化这一点,将其归结为最简单的例子:
我有一个具有 DHCP 服务器角色的 Windows 2008 R2 标准 DC。它通过各种 IPv4 范围分发 IP,这没有问题。
我希望
我希望有一种方法可以在设备获得 DHCP 地址租约时创建通知/事件日志条目/类似内容,并且该设备不是Active Directory 中已加入域的计算机。对我来说,它是否是自定义 Powershell 等并不重要。
底线 =我想要一种方法来知道非域设备何时在网络上,而无需使用 802.1X。 我知道这不会考虑静态 IP 设备。我确实有监控软件可以扫描网络并查找设备,但它的细节并不那么细致。
已完成的研究/考虑的选项
我没有看到内置日志记录存在任何此类可能性。
是的,我知道 802.1X,并且有能力在此位置长期实施它,但是我们距离这样的项目还有一段时间,虽然这可以解决网络身份验证问题,但这对我来说在 802.1X 目标之外仍然有帮助。
我四处寻找一些可能有用的脚本片段等,但我发现的东西让我相信我的 google-fu 目前正在让我失望。
我相信下面的逻辑是合理的(假设没有现成的解决方案):
- 设备接收 DHCP 地址
- 记录事件日志条目(DHCP 审计日志中的事件 ID 10 应该有效(因为我最感兴趣的是新租约,而不是续订):http://technet.microsoft.com/en-us/library/dd759178.aspx)
- 此时,某种脚本可能必须接管下面剩余的“步骤”。
- 以某种方式查询此 DHCP 日志以查找这些事件 ID 10(我希望推送,但我猜拉取是唯一的办法)
- 解析查询以获取分配新租约的设备名称
- 向 AD 查询设备名称
- 如果不是在 AD 中找到,发送通知电子邮件
如果有人知道如何正确地做到这一点,我将不胜感激。我并不是在寻求“给我代码”,而是想知道是否有上述列表的替代方案,或者如果我没有想清楚,是否有其他方法来收集这些信息。如果您有代码片段/PS 命令想要分享来帮助实现这一点,那就更好了。
答案1
非常感谢 ErikE 和这里的其他人,我走上了一条道路......我不会说这是正确的道路,但我提出的 Powershell 脚本可以解决问题。
如果有人需要,代码如下。只需手动运行它并指向每个 DHCP 服务器或安排它(再次指向脚本中的每个 DHCP 服务器)。
脚本的作用:
- 从 DHCP 服务器获取租约信息(ipv4 租约)
- 将租约输出到 csv 文件
- 读回该 CSV 文件以查询 AD
- 查询计算机的 AD
- 如果没有找到,则输出到新的 txt 文件
- 根据上面第 5 点中创建的文件创建一个唯一的列表最终 txt 文件(因为如果客户端注册多次或使用多个适配器,则可能会有重复)
- 将最终输出文件的内容通过电子邮件发送给管理员
你需要:
该脚本使用 AD 模块 ( import-module activedirectory
),因此最好在运行 DHCP 的 AD DC 上运行。如果不是这种情况,您可以安装 AD powershell 模块:http://blogs.msdn.com/b/rkramesh/archive/2012/01/17/how-to-add-active-directory-module-in-powershell-in-windows-7.aspx
您还需要在此处找到的 Quest 的 AD Powershell cmdlet: http://www.quest.com/powershell/activeroles-server.aspx。 先安装这些运行脚本,否则将会失败。
脚本本身(已清理,您需要设置一些变量以满足您的需要,例如输入文件名、要连接的域、要连接的 dhcp 服务器、接近末尾的电子邮件设置等):
# Get-nonADclientsOnDHCP.ps1
# Author : TheCleaner http://serverfault.com/users/7861/thecleaner with a big thanks for a lot of the lease grab code to Assaf Miron on code.google.com
# Description : This Script grabs the current leases on a Windows DHCP server, outputs it to a csv
# then takes that csv file as input and determines if the lease is from a non-AD joined computer. It then emails
# an administrator notification. Set it up on a schedule of your choosing in Task Scheduler.
# This helps non-802.1X shops keep track of rogue DHCP clients that aren't part of the domain.
#
# Input : leaselog.csv
# Output: Lease log = leaselog.csv
# Output: Rogue Clients with dupes = RogueClients.txt
# Output: Rogue Clients - unique = RogueClientsFinal.txt
$DHCP_SERVER = "PUT YOUR SERVER NAME OR IP HERE" # The DHCP Server Name
$LOG_FOLDER = "C:\DHCP" # A Folder to save all the Logs
# Create Log File Paths
$LeaseLog = $LOG_FOLDER+"\LeaseLog.csv"
#region Create Scope Object
# Create a New Object
$Scope = New-Object psobject
# Add new members to the Object
$Scope | Add-Member noteproperty "Address" ""
$Scope | Add-Member noteproperty "Mask" ""
$Scope | Add-Member noteproperty "State" ""
$Scope | Add-Member noteproperty "Name" ""
$Scope | Add-Member noteproperty "LeaseDuration" ""
# Create Each Member in the Object as an Array
$Scope.Address = @()
$Scope.Mask = @()
$Scope.State = @()
$Scope.Name = @()
$Scope.LeaseDuration = @()
#endregion
#region Create Lease Object
# Create a New Object
$LeaseClients = New-Object psObject
# Add new members to the Object
$LeaseClients | Add-Member noteproperty "IP" ""
$LeaseClients | Add-Member noteproperty "Name" ""
$LeaseClients | Add-Member noteproperty "Mask" ""
$LeaseClients | Add-Member noteproperty "MAC" ""
$LeaseClients | Add-Member noteproperty "Expires" ""
$LeaseClients | Add-Member noteproperty "Type" ""
# Create Each Member in the Object as an Array
$LeaseClients.IP = @()
$LeaseClients.Name = @()
$LeaseClients.MAC = @()
$LeaseClients.Mask = @()
$LeaseClients.Expires = @()
$LeaseClients.Type = @()
#endregion
#region Create Reserved Object
# Create a New Object
$LeaseReserved = New-Object psObject
# Add new members to the Object
$LeaseReserved | Add-Member noteproperty "IP" ""
$LeaseReserved | Add-Member noteproperty "MAC" ""
# Create Each Member in the Object as an Array
$LeaseReserved.IP = @()
$LeaseReserved.MAC = @()
#endregion
#region Define Commands
#Commad to Connect to DHCP Server
$NetCommand = "netsh dhcp server \\$DHCP_SERVER"
#Command to get all Scope details on the Server
$ShowScopes = "$NetCommand show scope"
#endregion
function Get-LeaseType( $LeaseType )
{
# Input : The Lease type in one Char
# Output : The Lease type description
# Description : This function translates a Lease type Char to it's relevant Description
Switch($LeaseType){
"N" { return "None" }
"D" { return "DHCP" }
"B" { return "BOOTP" }
"U" { return "UNSPECIFIED" }
"R" { return "RESERVATION IP" }
}
}
function Check-Empty( $Object ){
# Input : An Object with values.
# Output : A Trimmed String of the Object or '-' if it's Null.
# Description : Check the object if its null or not and return it's value.
If($Object -eq $null)
{
return "-"
}
else
{
return $Object.ToString().Trim()
}
}
function out-CSV ( $LogFile, $Append = $false) {
# Input : An Object with values, Boolean value if to append the file or not, a File path to a Log File
# Output : Export of the object values to a CSV File
# Description : This Function Exports all the Values and Headers of an object to a CSV File.
# The Object is recieved with the Input Const (Used with Pipelineing) or the $inputObject
Foreach ($item in $input){
# Get all the Object Properties
$Properties = $item.PsObject.get_properties()
# Create Empty Strings - Start Fresh
$Headers = ""
$Values = ""
# Go over each Property and get it's Name and value
$Properties | %{
$Headers += $_.Name + ","
$Values += $_.Value
}
# Output the Object Values and Headers to the Log file
If($Append -and (Test-Path $LogFile)) {
$Values | Out-File -Append -FilePath $LogFile -Encoding Unicode
}
else {
# Used to mark it as an Powershell Custum object - you can Import it later and use it
# "#TYPE System.Management.Automation.PSCustomObject" | Out-File -FilePath $LogFile
$Headers | Out-File -FilePath $LogFile -Encoding Unicode
$Values | Out-File -Append -FilePath $LogFile -Encoding Unicode
}
}
}
#region Get all Scopes in the Server
# Run the Command in the Show Scopes var
$AllScopes = Invoke-Expression $ShowScopes
# Go over all the Results, start from index 5 and finish in last index -3
for($i=5;$i -lt $AllScopes.Length-3;$i++)
{
# Split the line and get the strings
$line = $AllScopes[$i].Split("-")
$Scope.Address += Check-Empty $line[0]
$Scope.Mask += Check-Empty $line[1]
$Scope.State += Check-Empty $line[2]
# Line 3 and 4 represent the Name and Comment of the Scope
# If the name is empty, try taking the comment
If (Check-Empty $line[3] -eq "-") {
$Scope.Name += Check-Empty $line[4]
}
else { $Scope.Name += Check-Empty $line[3] }
}
# Get all the Active Scopes IP Address
$ScopesIP = $Scope | Where { $_.State -eq "Active" } | Select Address
# Go over all the Adresses to collect Scope Client Lease Details
Foreach($ScopeAddress in $ScopesIP.Address){
# Define some Commands to run later - these commands need to be here because we use the ScopeAddress var that changes every loop
#Command to get all Lease Details from a specific Scope - when 1 is amitted the output includes the computer name
$ShowLeases = "$NetCommand scope "+$ScopeAddress+" show clients 1"
#Command to get all Reserved IP Details from a specific Scope
$ShowReserved = "$NetCommand scope "+$ScopeAddress+" show reservedip"
#Command to get all the Scopes Options (Including the Scope Lease Duration)
$ShowScopeDuration = "$NetCommand scope "+$ScopeAddress+" show option"
# Run the Commands and save the output in the accourding var
$AllLeases = Invoke-Expression $ShowLeases
$AllReserved = Invoke-Expression $ShowReserved
$AllOptions = Invoke-Expression $ShowScopeDuration
# Get the Lease Duration from Each Scope
for($i=0; $i -lt $AllOptions.count;$i++)
{
# Find a Scope Option ID number 51 - this Option ID Represents the Scope Lease Duration
if($AllOptions[$i] -match "OptionId : 51")
{
# Get the Lease Duration from the Specified line
$tmpLease = $AllOptions[$i+4].Split("=")[1].Trim()
# The Lease Duration is recieved in Ticks / 10000000
$tmpLease = [int]$tmpLease * 10000000; # Need to Convert to Int and Multiply by 10000000 to get Ticks
# Create a TimeSpan Object
$TimeSpan = New-Object -TypeName TimeSpan -ArgumentList $tmpLease
# Calculate the $tmpLease Ticks to Days and put it in the Scope Lease Duration
$Scope.LeaseDuration += $TimeSpan.TotalDays
# After you found one Exit the For
break;
}
}
# Get all Client Leases from Each Scope
for($i=8;$i -lt $AllLeases.Length-4;$i++)
{
# Split the line and get the strings
$line = [regex]::split($AllLeases[$i],"\s{2,}")
# Check if you recieve all the lines that you need
$LeaseClients.IP += Check-Empty $line[0]
$LeaseClients.Mask += Check-Empty $line[1].ToString().replace("-","").Trim()
$LeaseClients.MAC += $line[2].ToString().substring($line[2].ToString().indexOf("-")+1,$line[2].toString().Length-1).Trim()
$LeaseClients.Expires += $(Check-Empty $line[3]).replace("-","").Trim()
$LeaseClients.Type += Get-LeaseType $(Check-Empty $line[4]).replace("-","").Trim()
$LeaseClients.Name += Check-Empty $line[5]
}
# Get all Client Lease Reservations from Each Scope
for($i=7;$i -lt $AllReserved.Length-5;$i++)
{
# Split the line and get the strings
$line = [regex]::split($AllReserved[$i],"\s{2,}")
$LeaseReserved.IP += Check-Empty $line[0]
$LeaseReserved.MAC += Check-Empty $line[2]
}
}
#endregion
#region Create a Temp Scope Object
# Create a New Object
$tmpScope = New-Object psobject
# Add new members to the Object
$tmpScope | Add-Member noteproperty "Address" ""
$tmpScope | Add-Member noteproperty "Mask" ""
$tmpScope | Add-Member noteproperty "State" ""
$tmpScope | Add-Member noteproperty "Name" ""
$tmpScope | Add-Member noteproperty "LeaseDuration" ""
#endregion
#region Create a Temp Lease Object
# Create a New Object
$tmpLeaseClients = New-Object psObject
# Add new members to the Object
$tmpLeaseClients | Add-Member noteproperty "IP" ""
$tmpLeaseClients | Add-Member noteproperty "Name" ""
$tmpLeaseClients | Add-Member noteproperty "Mask" ""
$tmpLeaseClients | Add-Member noteproperty "MAC" ""
$tmpLeaseClients | Add-Member noteproperty "Expires" ""
$tmpLeaseClients | Add-Member noteproperty "Type" ""
#endregion
#region Create a Temp Reserved Object
# Create a New Object
$tmpLeaseReserved = New-Object psObject
# Add new members to the Object
$tmpLeaseReserved | Add-Member noteproperty "IP" ""
$tmpLeaseReserved | Add-Member noteproperty "MAC" ""
#endregion
# Go over all the Client Lease addresses and export each detail to a temporary var and out to the log file
For($l=0; $l -lt $LeaseClients.IP.Length;$l++)
{
# Get all Scope details to a temp var
$tmpLeaseClients.IP = $LeaseClients.IP[$l] + ","
$tmpLeaseClients.Name = $LeaseClients.Name[$l] + ","
$tmpLeaseClients.Mask = $LeaseClients.Mask[$l] + ","
$tmpLeaseClients.MAC = $LeaseClients.MAC[$l] + ","
$tmpLeaseClients.Expires = $LeaseClients.Expires[$l] + ","
$tmpLeaseClients.Type = $LeaseClients.Type[$l]
# Export with the Out-CSV Function to the Log File
$tmpLeaseClients | out-csv $LeaseLog -append $true
}
#Continue on figuring out if the DHCP lease clients are in AD or not
#Import the Active Directory module
import-module activedirectory
#import Quest AD module
Add-PSSnapin Quest.ActiveRoles.ADManagement
#connect to AD
Connect-QADService PUTTHEFQDNOFYOURDOMAINHERE_LIKE_DOMAIN.LOCAL | Out-Null
# get input CSV
$leaselogpath = "c:\DHCP\LeaseLog.csv"
Import-csv -path $leaselogpath |
#query AD for computer name based on csv log
foreach-object `
{
$NameResult = Get-QADComputer -DnsName $_.Name
If ($NameResult -eq $null) {$RogueSystem = $_.Name}
$RogueSystem | Out-File C:\DHCP\RogueClients.txt -Append
$RogueSystem = $null
}
Get-Content C:\DHCP\RogueClients.txt | Select-Object -Unique | Out-File C:\DHCP\RogueClientsFinal.txt
Remove-Item C:\DHCP\RogueClients.txt
#send email to netadmin
$smtpserver = "SMTP SERVER IP"
$from="[email protected]"
$to="[email protected]"
$subject="Non-AD joined DHCP clients"
$body= (Get-Content C:\DHCP\RogueClientsFinal.txt) -join '<BR> <BR>'
$mailer = new-object Net.Mail.SMTPclient($smtpserver)
$msg = new-object Net.Mail.MailMessage($from,$to,$subject,$body)
$msg.IsBodyHTML = $true
$mailer.send($msg)
希望这对别人有帮助!
答案2
好吧,我不确定我是否遵守了这里的礼仪,但我还是发布了第二个答案,而不是编辑我之前的答案,因为它确实包含了一些信息,即使与本案无关,也可能对某些人有用。如果这让我在这个论坛上显得很白痴,请随时告诉我我的错误做法。
问题分为几个部分,以下是我认为最有趣的部分的建议。如果没有日志中的示例,这是我能做的最好的,所以这只是建议而不是解决方案。
要解析日志,请使用get-content
参数-wait
。就我的用例而言,在错误日志中查找错误就足够了。
这是对我自己的用例有用的方法,请原谅我的格式:
get-content E:\temp13\log.txt -tail(1) -wait | where {$_ -match "ERROR"} |
foreach {
send-mailmessage `
-port 25 `
-smtpserver my.mail.server `
-from [email protected] `
-to [email protected] `
-subject "test logmonitor" `
-body "ERROR found: $_" `
}
您需要以$_ -match "ERROR"
某种方式将日志 ID 字段和计算机名称分开。我不确定现在如何以最佳方式进行操作,但由于where-object -match
提供正则表达式支持,我猜这可能是一个选择。您也可以从将 $_ 变量存储在另一个新变量中开始,以便能够在管道中、嵌套的 foreach 循环等中随时获取它。
假设您可以获得计算机名称,我猜get-adcomputer
cmdlet 将是您查询 AD 的最简单方法(import-module activedirectory
),并且我猜出现错误时会发送邮件?
当然,在您的情况下使用import-csv
会更加优雅,但我不知道有任何方法可以跟踪它(如果有人碰巧读到这篇文章并且知道这个技巧,那么请分享)。
答案3
假设您确定事件 ID,并且 DHCP 日志中没有其他事件记录到此 ID,但您感兴趣的事件除外,推送确实是一个选择。
1) 打开服务器管理器,转到事件查看器中的 DHCP 日志。
2) 找到您想要附加操作的代表性条目。选择它并右键单击。
3) 选择“将任务附加到此事件”。
4)任务创建向导打开,从那里将其带走...
实际上有一个明确的电子邮件选项,但如果您需要更多逻辑,您当然可以自由使用启动程序选项来启动 powershell.exe 并向其附加脚本。如果您需要指导,有很多关于如何让任务管理器运行 powershell 脚本的优秀 google 指南。
我看到的直接替代方案是使用 pull,通过按预定间隔使用 powershell 解析事件日志。“Microsoft Scripting Guy”,又名 Ed Wilson 写了一些关于如何使用不同版本的 powershell 中可用的 cmdlet 解析事件日志的精彩博客文章,因此我的建议是将他的博客作为起点。
至于实际的 cmdlet,我现在没有时间拿出我存储的方便的片段,但会在一两天后再次查看,如果没有其他人提供一些精心挑选的片段,或者您还没有自己解决所有问题,我可能会做出贡献 :-)