65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Powershell 清理用户配置文件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2023 年 3 月 9 日

MIT

5分钟阅读

viewsIcon

23095

如何清理计算机上的用户配置文件

引言

作为我日常工作的一部分,我在一个受控环境中管理服务器,用户通过 RDP 访问这些服务器。因此,每个 RDP 服务器上都创建了大量的用户配置文件。此外,这些服务器的 C 盘相对较小,用户配置文件会影响系统。出于合规性和文档记录的原因,将用户配置文件移动到其他驱动器选项不可行。

好消息是,这些用户配置文件不包含关键数据。因此,偶尔清理它们不会有问题。然而,这些用户配置文件数量众多,分布在很多服务器上,手动执行此操作是不可行的。

这是脚本成为一个好主意的绝佳场景。

前提条件

所需的脚本具有以下要求:

  1. 某些用户配置文件不得删除,这基于组的成员资格。在此示例中,这意味着不删除属于管理员帐户的配置文件。
  2. 已登录用户配置文件不得删除,也不删除操作系统标记为“特殊”的用户帐户(如服务帐户)的配置文件。
  3. 只删除属于特定 OU 的用户的配置文件。这是保证配置文件属于实际用户,而不是服务帐户或本地帐户的一种方式。

WMI 与 CIM

在深入细节之前,我们需要了解与 Windows 交互的方法。虽然我们可以通过删除配置文件文件夹来删除配置文件,但这并不是一个干净的操作,因为它不会清理注册表。因此,我们需要与 Windows 本身交互,并要求 Windows 为我们删除配置文件,这与我们在控制面板上手动执行的操作类似。

过去,WMI 会是显而易见的选择。然而,WMI 正在被弃用。与其在此处详细介绍,不如链接到这篇优秀的文章,它解释了它们之间的区别以及为什么 CIM 是未来而 WMI 是过去。

对于我们的目的,我们使用 CIM,因为有一个非常方便的方法可以通过 `Get-CimObject` 检索用户配置文件列表,然后通过将 CIM 对象传递给 `Remove-CimInstance` 来删除选定的用户配置文件。

构建脚本

脚本由多个部分组成。

设置先决条件

在我的环境中,排除列表包含管理员组作为示例。根据您自己的需求,您可以为需要永久用户配置文件的帐户添加特定的应用程序组。

在脚本的某个时候,我们需要评估用户所属的组列表是否不包含排除列表中的组。脚本大师可能会用一个清晰的单行代码实现这一点,但为了我自己的理智,我将该检查放在一个函数中,以保持脚本其余部分的清晰易懂。

Import-Module ActiveDirectory

function MemberOfExcludedGroup
{
    param ($memberof, $exclusionlist)
    $retval = $false
    $exclusionlist | foreach {if($memberof -match $_ ) {$retval = $true}}
    $retval
}

#if a user belongs to one of these groups, 
#their profile is automatically excluded from removal
$exclusionlist = @("Domain Admins", "Administrators")

`MemberOfExcludedGroup` 函数在用户属于排除组之一时返回 `$true`,否则返回 `$false`。

获取范围内用户列表

找出哪些用户可能在清理过程的范围内是第一步。我们通过以下代码段来实现这一点。该代码段用英语表示‘获取指定 OU 中不属于某个排除组的所有用户,并获取他们的 `SID`’。

#get the list of actual DeltaV users. 
#We only remove the profile for actal user accounts, not for
#internal service accounts or other accounts not in the DeltaV Users OU
$userOUpath = (Get-ADOrganizationalUnit -Filter 'Name -like "DeltaV Users"').DistinguishedName
$users = Get-ADUser -Filter * -SearchBase $userOUpath  -properties memberof, SID |
    where {! (MemberOfExcludedGroup $_.MemberOf $exclusionlist)} |
    foreach { $_.SID.Value }

起初,通过 `SID` 指定范围内的用户可能显得有些奇怪。这是有非常充分的理由的。

在步骤 2 中,我们需要将用户配置文件与它们所属的用户进行匹配。在 Windows 中,它们是通过它们的 `SID` 关联的。遗憾的是,由于本地安全机构 (LSA) 执行 `SID` 查找的方式,获取 `SID` 的名称速度非常慢;慢到脚本会长时间冻结,尝试为 `SID` 检索名称和域。我们谈论的是几百个用户需要半小时或更长时间。

#this takes ages to complete
gwmi win32_userprofile |
    foreach {
        ($act = gwmi win32_useraccount -Filter “sid = '$($_.sid)'"); $act.Name;$act.Domain }

然而,这里有一个好消息:我们并不关心名称。

与其将 `SID` 转换为名称,然后检查该名称是否是域用户,以及 `SID` 的域是否是用户的域,我们只需获取范围内所有域用户的 `SID`,然后检查配置文件 `SID` 是否在此列表中。此查找速度极快。

获取配置文件列表

现在我们有了范围内的用户列表,我们将其与范围内的配置文件进行匹配。

Get-CimInstance win32_userprofile |
    where { $users.Contains($_.SID) -and
            !$_.Special -and
            !$_.Loaded } |
            foreach {"Deleting profile $($_.LocalPath)"; Remove-CimInstance $_} 

我们通过 WMI 和 `win32_userprofile` 类获取用户配置文件列表。从该列表中,我们只保留属于范围内用户的配置文件,前提是该配置文件未被标记为特殊或已加载(用户当前已登录)。

然后,通过另一个 WMI 函数 `Remove-CimInstance` 删除剩余的 `userprofiles`。

将所有内容整合

将所有这些内容放在一起,最终的脚本非常简单。

Import-Module ActiveDirectory

function MemberOfExcludedGroup
{
    param ($memberof, $exclusionlist)

    $retval = $false
    $exclusionlist | foreach {if($memberof -match $_ ) {$retval = $true}}

    return $retval
}

#if a user belongs to one of these groups, their profile 
#is automatically excluded from removal
$exclusionlist = @("Domain Admins", "Administrators")

#get the list of actual DeltaV users. 
#We only remove the profile for actual user accounts, not for
#internal service accounts or other accounts not in the DeltaV Users OU
$userOUpath = (Get-ADOrganizationalUnit -Filter 'Name -like "DeltaV Users"').DistinguishedName
$users = Get-ADUser -Filter * -SearchBase $userOUpath  -properties memberof, SID |
    where {! (MemberOfExcludedGroup $_.MemberOf $exclusionlist)} |
    foreach { $_.SID.Value }

#Get the full list of profile folders in the C:\Users folder and examine every one
Get-CimInstance win32_userprofile |
    where { $users.Contains($_.SID) -and
            !$_.Special -and
            !$_.Loaded } |
            foreach {"Deleting profile $($_.LocalPath)"; Remove-CimInstance $_} 

关注点

我不是脚本专家。我是一名软件开发者。这意味着我习惯于变量具有具体类型,或者函数总是有一个以返回值结尾的控制路径等等。在脚本中,这一点不那么严格。我毫不怀疑我的背景影响了我处理脚本的方式。

如果您是脚本专家,并且看到任何错误/低效或其他问题,我很乐意听取您的意见。

该脚本已获得 MIT 许可,尽情使用吧。

历史

  • 2023 年 3 月 9 日:第一个版本
© . All rights reserved.