越接触PowerShell感觉越喜欢这门脚本语言,简单易懂,功能强大,操作也方便,同时得益于微软的鼎力支持,在不同的微软产品平台都可以使用,如果想研究微软这方面的东西,会点PowerShell绝对是好处多多。

   之前也写了一些关于PowerShell的文章,也相当于是自己不断摸索的过程,最近也陆陆续续写了一些脚本,有一些是工作环境里使用的,没办法拿出来分享,有一些是不同环境里都可以使用的,所以决定拿出来分享一下,脚本都很简单,写的也绝对算不上专业,只是基本的功能可以实现。

   今天和大家分享的是写的一个比较简单的脚本,主要应用的场景也很常见,就是AD环境中,如果用户密码将要过期的话如何即使提醒用户即使更改密码,当然Windows会定期的自动提醒,这也是很多时候我们不需要这种脚本的一个原因,但是如果说是不同的环境呢?比如像我们常用的企业AD环境是公司的IT在运维,我们自己还会负责运维其他的AD环境,这些环境可能不会每天都登陆,甚至很长时间也不会登陆一次,这时候如果密码过期的话我们是看不到提醒的,所以这种情况下就很需要我们有这样一套密码过期提醒的机制来督促我们修改密码,同时如果是IT基础很薄弱的公司,我们还可以通过在邮件模板中加入修改密码方法的方式来告诉普通用户如何修改他们的密码,这样的话可以很大程度上减轻IT的负担。

   这种类似的脚本实现的方法并不止一种,网上也有很多类似的脚本都可以实现这样的功能,但是这里要注意的一点是,有一些脚本因为写的年头比较久远了,之后微软在AD这方面又进行了一些更新,所以在一些比较新的环境里(比如Server 2012或者R2等)有些脚本可能就显得不会特别严谨,比如微软之前推出的颗粒化密码策略的功能,就有可能会导致对于密码过期时间的判断出现偏差。

   这类脚本的实现方法基本都很简单,思路也都差不多,就是找出用户上次密码重置的时间,然后再加上允许密码最长的期限,这样算出来就是用户密码下一次的过期时间,但是如果是颗粒化密码策略的话实际上用户的允许密码最长期限就会不同于AD里的其他账户,在这一点上如果不注意,一视同仁的话那么就会导致最后的判断出现问题。

   下边开始进入正题了,把脚本粘出来大家自己看一下就可以了,基本上都很简单

   

Function LogFile ($output, $initLog)
{
	if ($initLog -eq $True)
	{
		$input | out-file -filepath $output -encoding default -width 17384
	}
	else
	{
		$input | out-file -filepath $output -encoding default -width 17384 -append
	}
}

function Send-Report
{
	param($LogConent,$LogPath,$MailAddress)
	try
	{
		Send-MailMessage -From "NO-Reply@contoso.com" -To $MailAddress -Subject 'Contoso Password check report' -Body $LogConent -Priority 'High' -SmtpServer mail.contoso.com -Port 25 -ErrorAction 'SilentlyContinue'
	}
	catch
	{
		$ErrorMessage = $Error[0].Exception.Message
		Write-Host -ForegroundColor 'Red' "$(Get-Date -uFormat %Y%m%d-%H:%M:%S)" $ErrorMessage
		("$(Get-Date -uFormat %Y%m%d-%H:%M:%S): " + $ErrorMessage) | LogFile -output $LogPath
	}
}


#Main Code
#Import ActiveDirectory module
Import-Module ActiveDirectory


#Log initialization
[string]$LogDate = Get-Date -Format "yyyyMMdd"
$LogPath = "C:\PasswordLogs\DomainPasswordLog$LogDate.txt"
if ((Test-Path 'C:\PasswordLogs') -eq $false)
{
	New-Item -ItemType directory 'C:\PasswordLogs' | Out-Null
}


#======================================================================================
#Get MaxPasswordAge
$RootDSE = Get-ADRootDSE
$PasswordPolicy = Get-ADObject $RootDSE.defaultNamingContext -Property maxPwdAge
$maxPwdAge = $PasswordPolicy.maxPwdAge/-864000000000
if (($maxPwdAge -eq 0) -or ($maxPwdAge -eq $null))
{
	$ErrorMessage = "MaxPasswordAge is not correct"
	Write-Host -ForegroundColor 'Red' "$(Get-Date -uFormat %Y%m%d-%H:%M:%S)" $ErrorMessage
	("$(Get-Date -uFormat %Y%m%d-%H:%M:%S): " + $ErrorMessage) | LogFile -output $LogPath
	$LogConent = Get-Content $LogPath -raw
	Send-Report -LogConent $LogConent -LogPath $LogPath -MailAddress 'abc@contoso.com'
	exit
}
#======================================================================================
#Check userlist
#我这里的用户列表是写在一个txt文档里的,这是因为在我的环境中大部分用户是不需要这种邮件提醒的,他们的账户会由我们负责维护
#如果需要在AD里检索需要检查的用户的话可以直接这样写$userList=Get-ADUser -Filter *|Select-Object -ExpandProperty SamAccountName
#这样的话下边这段就不需要了
$userList = "C:\Users\abc\UserList.txt"
if ((Test-Path $UserList) -eq $false)
{
	$ErrorMessage = "Can't find userList.txt"
	Write-Host -ForegroundColor 'Red' "$(Get-Date -uFormat %Y%m%d-%H:%M:%S)" $ErrorMessage
	("$(Get-Date -uFormat %Y%m%d-%H:%M:%S): " + $ErrorMessage) | LogFile -output $LogPath
	$LogConent = Get-Content $LogPath -raw
	Send-Report -LogConent $LogConent -LogPath $LogPath -MailAddress 'abc@contoso.com'
	exit
}

#======================================================================================

#这里如果是使用检索AD用户的方法的话可以直接写
#foreach($user in $userlist)替代get-content即可

Get-Content $UserList | %{
	$name = $null
	$userinfo = $null
	$ExpireDate = $null
	$PasswordSetDate = $null
	$Today = $null
	$leftDays = $null
	$body = $null
	$subject = $null
	$IndividualPasswordPolicy = $null
	$OutputMessage = $null
	$name = $_
	$userinfo = Get-ADUser -Identity $name -Properties *
    #这里首先判断该用户信息是否存在,如果不存在直接进行记录即可
	if ($userinfo -eq $null)
	{
		$ErrorMessage = $name + ": " + $Error[0].Exception.Message
		Write-Host -ForegroundColor 'Red' "$(Get-Date -uFormat %Y%m%d-%H:%M:%S)" $ErrorMessage
		("$(Get-Date -uFormat %Y%m%d-%H:%M:%S): " + $ErrorMessage) | LogFile -output $LogPath
	}
	else
	{
		if ($userinfo.PasswordNeverExpires -eq $true)
		{
		    #这里记录谁的密码被设置为永久不过期了
			$ErrorMessage = "$name's Password has been set to NeverExpires"
			Write-Host -ForegroundColor 'Cyan' "$(Get-Date -uFormat %Y%m%d-%H:%M:%S)" $ErrorMessage
			("$(Get-Date -uFormat %Y%m%d-%H:%M:%S): " + $ErrorMessage) | LogFile -output $LogPath
		}
		else
		{
			#这里会读取颗粒化密码策略的设置,它的优先级应该高于域策略的设置
			$IndividualPasswordPolicy = (Get-AduserResultantPasswordPolicy $name)
			if ($IndividualPasswordPolicy -ne $null)
			{
				$maxPwdAge = $IndividualPasswordPolicy.MaxPasswordAge.TotalDays
			}
			$PasswordSetDate = $userinfo.PasswordLastSet
			$ExpireDate = $PasswordSetDate.AddDays($maxPwdAge)
			$Today = Get-Date
			
			#对比过期时间和今天,得出的数值就是还有多少天过期
			$leftDays = (New-TimeSpan -Start $Today -End $ExpireDate).Days
			
			if ($leftDays -lt 0)
			{
				$body = "
    			Dear $name ,
   		 		<p> Your Password has expired!!.<br>
    			Please change your Password as soon as possible so that you can work normally<br>
   				<p>Thanks, <br> 
    			</P>"
				
				$subject = "Your Password has expired!!"
				$OutputMessage = "$(Get-Date -uFormat %Y%m%d-%H:%M:%S): $name's Password has expired"
				Write-Output $OutputMessage | LogFile -output $LogPath
			}
			elseif ($leftDays -eq 1)
			{
				$body = "
    			Dear $name ,
   		 		<p> Your Password will expire in <b><font size=`"20px`" color=`"red`"> $leftDays </font></b> Day!!.<br>
    			Please change your Password as soon as possible so that you can work normally <br>
   				<p>Thanks, <br> 
    			</P>"
				
				$subject = "Your Password will expire in $leftDays day!!"
				$OutputMessage = "$(Get-Date -uFormat %Y%m%d-%H:%M:%S): $name's Password will expire in $leftDays day"
				Write-Output $OutputMessage | LogFile -output $LogPath
			}
			elseif ($leftDays -le 10)
			{
				$body = "
    			Dear $name ,
   		 		<p> Your Password will expire in <b><font size=`"20px`" color=`"red`"> $leftDays </font></b> Days!!.<br>
    			Please change your Password as soon as possible so that you can work normally <br>
   				<p>Thanks, <br> 
    			</P>"
				
				$subject = "Your Password will expire in $leftDays days"
				$OutputMessage = "$(Get-Date -uFormat %Y%m%d-%H:%M:%S): $name's Password will expire in $leftDays days"
				Write-Output $OutputMessage | LogFile -output $LogPath
			}
			else
			{
				$OutputMessage = "$(Get-Date -uFormat %Y%m%d-%H:%M:%S): $name's Password will expire in $leftDays days"
				Write-Output $OutputMessage | LogFile -output $LogPath
			}
			
			
			#这里设置的是如果10天以内过期的话就会发送提醒
			if ($leftDays -le 10)
			{
			    #注意如果EmailAddress为空的话就需要自己处理如何找到邮件发送的地址了
				$MailAddress =   $userinfo.EmailAddress
				if ($MailAddress -ne $null)
				{	
					try
					{
						Send-MailMessage -From "No-Reply@contoso.com" -To $MailAddress -Subject $subject -Body $body -BodyAsHtml -Priority 'High' -SmtpServer mail.contoso.com -Port 25 -ErrorAction 'SilentlyContinue'
					}
					catch
					{
						$ErrorMessage = $Error[0].Exception.Message
						Write-Host -ForegroundColor 'Red' "$(Get-Date -uFormat %Y%m%d-%H:%M:%S)" $ErrorMessage
						("$(Get-Date -uFormat %Y%m%d-%H:%M:%S): " + $ErrorMessage) | LogFile -output $LogPath
					}
				}
				
				
			}
			
			
		}
		
		
	}
	
	
	
}

#最后把这份报告发送给IT管理员
if ((Test-Path $LogPath) -eq $true)
{
	$LogConent = Get-Content $LogPath -Raw
	Send-Report -LogConent $LogConent -LogPath $LogPath -MailAddress 'it@contoso.com'
}

之后设置一个任务计划,每天运行这个脚本就可以了。

基本上功能就实现了,总体来说比较简单