Windows Ghost Server Check

Serkan Dündar
Turk Telekom Bulut Teknolojileri
5 min readSep 7, 2023

Windows sunucular üzerinde kullanımına bağlı olarak bu sunucunun aktif olarak kullanıldığını veya kullanılmadığı ayrımını yapmak için powershell üzerinden script ile belirlediğimiz parametreler yoluyla kontrolünü sağlayabiliriz. Bunun için aşağıda örnek olarak koyacağım bir script olacak ve detaylı bir şekilde görebilirsiniz. Burada kullanılan script parametrelerine ek olarak kendi kontrol mekanizmalarını ekleyerek scripti genişletebilirsiniz.

Bu scripti değişik yöntemler ile sunuculara gönderebilir veya sunucu localinde gerekli küçük düzeltmelerle çalıştırabilirsiniz. Bu sayede sunucu üzerinde herhangi bir aksiyon varsa kullanım durumunda veya kullanım durumunda değil diye ayrım yapabilirsiniz.

kullandığınız DB ile entegre ederek bunların arşivini tutabilir veya sunucularınız üzerinde kullanım durumunu sahiplerine sorarak kullanım dışı ise kapatarak gereksiz kaynak kullanımının önüne geçebilirsiniz.

#region Parameters
# Set Domain Info
[string]$DomainControllerFQDN = (Get-WmiObject -Class Win32_ComputerSystem).Domain

If($DomainControllerFQDN -in ('kontrol etmek istenen domain','yada kontrol etmek istenen domainler'))
{
[string]$DomainFQDN = "domain fqdn bilgisi."
}
Else
{
[string]$DomainFQDN = $DomainControllerFQDN
}
[int]$LdapPort = 3268
[int]$LastLogonDay = 360

[int]$DelaySec = Get-Random -Maximum 20 #800

# Set Groups and default admins
[array]$LocalGroupList = ('hangi kullanıcı gruplarında kullanıcı kontrolleri yapmak isteniyorsa buraya yazılacak.')
[array]$ExcludeList = (
"hangi kullanıcı grupları exclude edilmek isteniyorsa o gruplar buraya yazılacak.",
"ve admin hesabınız neyse o buraya yazılacak. "
)
#hangi kullanıcıların bu scriptte exclude edilmesi gerekiyorsa sırası ile aşağıya doğru yazılarak bu listeye eklenecek.
[array]$UserExcludeList = (
"kullanıcı sicil"
)

# Set Log files
[string]$logFile = "$env:windir\nereye log yazmak istiyorsak oranın lokasyonu$(Get-Date -Format yyyyMM).log"
#endregion

#region Functions

#loglama yapmak için burayı kullanıyoruz.
Function Write-Log
{
Param(
[string]$text
)

Write-Output "[$(Get-Date -Format G)] - $text" >> $logFile
#Write-Host "[$(Get-Date -Format G)] - $text"
}

Function Set-LDAPSearchBase
{
$DomainInfo = $DomainFQDN.Split('.')
[string]$ADSearchRoot = ""

$i = 0
ForEach($item in $DomainInfo)
{
$i ++
$Root += "DC=$item"

If($i -lt $DomainInfo.Count)
{
$Root += ","
}
Else
{
$Root = "LDAP://$($DomainControllerFQDN):$($LdapPort)/$($Root)"
}
}

Write-Log -text "[Set-LDAPSearchBase] - $Root"
Return $Root
}

Function Check-EnabledGroupMember
{
Param(
[string]$GroupName
)

Write-Log -text "[Check-EnabledGroupMember] - GroupName: $($GroupName)"

$GroupMembers = Get-LocalGroupMember -Name $GroupName | Where-Object{$_.Name -notin $ExcludeList}

If($GroupMembers)
{
# Member found
ForEach($item in $GroupMembers)
{
Write-Log -text "[Check-EnabledGroupMember] - Member: $($item.Name)"

$MemberName = ($item.Name).Split("\")[1]
$MemberDomain = ($item.Name).Split("\")[0]

If($item.ObjectClass -eq "User")
{
# Member is a user
If($MemberName -notin $UserExcludeList)
{
# Admin User found. Check isEnabled
$isEnabled = $false

If($MemberDomain -eq $env:ComputerName)
{
#Local User
If((Get-LocalUser -Name $MemberName).Enabled -eq $true)
{
$isEnabled = $true
}
}
Else
{
#AD
$Searcher.Filter = "(&(objectCategory=user)(name=$($MemberName)))"
$UserInfo = $Searcher.FindAll().Properties

If($UserInfo.useraccountcontrol -in ("512", "66048"))
{
$isEnabled = $true
}
}

If($isEnabled -eq $true)
{
$EnabledAdminUser = "$($item.Name)"

Write-Log -text "[Check-EnabledGroupMember] - Enabled - UserName: $($item.Name)"
Break
# Enabled admin user found, remaining user will not be checked.
}
}
}
ElseIf($item.ObjectClass -eq "Group")
{
# Member is a AD group
# Get AD Group DN
$Searcher.Filter = "(&(objectCategory=group)(name=$($MemberName)))"
$GroupInfo = $Searcher.FindAll().Properties

# Get group members (recursive)
$Searcher.Filter = "(&(memberof:1.2.840.113556.1.4.1941:=$($GroupInfo.distinguishedname))(objectCategory=user))"
$GroupMembers = $Searcher.FindAll().Properties

ForEach($gMember in $GroupMembers)
{
If($gMember.cn -notin $UserExcludeList)
{
# Admin User found. Check isEnabled
$isEnabled = $false

If($gMember.useraccountcontrol -in ("512","66048"))
{
$isEnabled = $true
Break
# Enabled admin user found, remaining user will not be checked.
}
}
}

If($isEnabled -eq $true)
{
$EnabledAdminUser = "$($gMember.cn)($($item.Name))"

Write-Log -text "[Check-EnabledGroupMember] - Enabled - GroupName: $($item.Name) - UserName: $($gMember.cn)"
Break
# Enabled admin user found, remaining user will not be checked.
}
}
}
}

Return $EnabledAdminUser
}

#last logon date aldığımız fonksiyon
Function Check-LastLogonDate
{
# Get list of userprofiles
$ActiveUserName = $null
$ActiveUserPolicyDate = $null

$UserProfiles = Get-WmiObject -Class Win32_UserProfile -Filter "Special='False'" | Where-Object{$_.SID -like 'S-1-5-21-*'} | Select SID,LocalPath
Write-Log -text "[Check-LastLogonDate] - ProfileCount: $($UserProfiles.Count)"

ForEach($item in $UserProfiles)
{
Try
{
[datetime]$UserLastPolicyDate = '2001-01-01 00:00:00'
$User = ((New-Object System.Security.Principal.SecurityIdentifier("$($item.SID)")).Translate([System.Security.Principal.NTAccount])).Value

If(($User.Split("\"))[1] -notin $UserExcludeList)
{
# Profile found
$SID = ($item.SID).Replace('-','_')
$RSOP = Get-WmiObject -Namespace "root\RSOP\User\$SID" -Query "Select displayName, beginTime, endTime From RSOP_ExtensionStatus Where displayName = 'Group Policy Infrastructure'" -ErrorAction Stop | Select displayName, beginTime, endTime

Try
{
$PolicyUpdateDate = [datetime]::ParseExact(($RSOP.endTime).Substring(0,14), 'yyyyMMddHHmmss', $null)
}
Catch
{
$PolicyUpdateDate = [datetime]::ParseExact(($RSOP.beginTime).Substring(0,14), 'yyyyMMddHHmmss', $null)
}

If($UserLastPolicyDate -lt $PolicyUpdateDate)
{
$UserLastPolicyDate = $PolicyUpdateDate
}

If(((Get-Date) - $UserLastPolicyDate).Days -lt $LastLogonDay)
{
$ActiveUserName = $User
$ActiveUserPolicyDate = $UserLastPolicyDate

Write-Log -text "[Check-LastLogonDate] - Active UserName: $User - LastPolicyDate: $UserLastPolicyDate"
Break
}
Else
{
Write-Log -text "[Check-LastLogonDate] - Inactive UserName: $User - LastPolicyDate: $UserLastPolicyDate"
}
}
}
Catch
{
}
}

Return $ActiveUserName, $ActiveUserPolicyDate
}

#karar mekanizması
Function Set-CM_isGhost
{
Try
{
# Create new WMI class
$NewClassName = 'classa vermek istediğimiz isim.'
Remove-WmiObject $NewClassName -ErrorAction SilentlyContinue

$newClass = New-Object System.Management.ManagementClass ("root\cimv2", [String]::Empty, $null) -ErrorAction Stop
$newClass["__CLASS"] = $NewClassName
$newClass.Qualifiers.Add("Static", $true)
$newClass.Properties.Add("Ghost", [System.Management.CimType]::String, $false)
$newClass.Properties.Add("EnabledAdminUserName", [System.Management.CimType]::String, $false)
$newClass.Properties.Add("ActiveAdminUserName", [System.Management.CimType]::String, $false)
$newClass.Properties.Add("ActiveAdminUserPolicyDate", [System.Management.CimType]::String, $false)
$newClass.Properties["Ghost"].Qualifiers.Add("Key", $true)
$newClass.Put() | Out-Null

$Properties = @{
Ghost = $($isGhost)
EnabledAdminUserName = $($EnabledAdminUserName)
ActiveAdminUserName = $($ActiveAdminUserName)
ActiveAdminUserPolicyDate = $($ActiveAdminUserPolicyDate)
}

Set-WmiInstance -Namespace root\cimv2 -Class $NewClassName -Arguments $Properties -ErrorAction Stop | Out-Null
Write-Log -Text "[Set-CM_isGhost] - Success"
}
Catch
{
Write-Log -Text "[Set-CM_isGhost] - Failed - Error: $($_.Exception.Message)"
}
}


#endregion

# Main -----------------------------------------
<#
- grupların içerisinde yetki kullanıcı yoksa => Ghost
- grupların içerisinde yetki kullanıcı varsa
- userprofile oturum açma süresesi 1 yıldan daha uzun bir süre ise => Ghost
#>

Write-Log -text "- Wait $($DelaySec) ----------------------------------"
Start-Sleep -Seconds $DelaySec

# Set AD Searcher Object
$ADSearchRoot = Set-LDAPSearchBase
$Searcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $ADSearchRoot
$Searcher.PropertiesToLoad.Add("cn") | Out-Null
$Searcher.PropertiesToLoad.Add("distinguishedname") | Out-Null
$Searcher.PropertiesToLoad.Add("useraccountcontrol") | Out-Null
$Searcher.SearchScope = "Subtree"

# Check "Administrators" and "Remote Desktop Users" group for active group member
$AdminUserExist = $false
ForEach($item in $LocalGroupList)
{
$EnabledAdminUserName = Check-EnabledGroupMember -GroupName $item

If($EnabledAdminUserName)
{
$AdminUserExist = $true

Break
# Enabled admin user found, remaining group will not be checked.
}
}

# Check User's last logon date
If($AdminUserExist -eq $true)
{
$ActiveAdminUser = Check-LastLogonDate

If($ActiveAdminUser[0] -ne $null)
{
# Not Ghost
$isGhost = $false
$ActiveAdminUserName = $ActiveAdminUser[0]
$ActiveAdminUserPolicyDate = $ActiveAdminUser[1]
}
Else
{
# Ghost
$isGhost = $true
$ActiveAdminUserName = $null
$ActiveAdminUserPolicyDate = $null
}
}
Else
{
# Ghost
$isGhost = $true
$EnabledAdminUserName = $null
$ActiveAdminUserName = $null
$ActiveAdminUserPolicyDate = $null
}

# Show result
Set-CM_isGhost
Write-Log -text "Ghost: $($isGhost) - EnabledAdminUserName: $($EnabledAdminUserName) - ActiveAdminUserName: $($ActiveAdminUserName) - ActiveAdminUserPolicyDate: $($ActiveAdminUserPolicyDate)"
Write-Host "Ghost: $($isGhost) - EnabledAdminUserName: $($EnabledAdminUserName) - ActiveAdminUserName: $($ActiveAdminUserName) - ActiveAdminUserPolicyDate: $($ActiveAdminUserPolicyDate)"

# Send HWI
$ActionID = '{00000000-0000-0000-0000-000000000001}'
$SMSCli = [wmiclass] "root\ccm:SMS_Client"
$SMSCli.TriggerSchedule($ActionID) | Out-Null

Umarım faydalı olur.

Saygılar.

--

--