You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SharePoint 2019中PowerShell脚本运行速度异常问题排查

SharePoint 2019脚本后期处理变慢的原因及优化方案

你的脚本运行后期变慢,主要是SharePoint对象未正确释放导致内存泄漏、重复AD查询无缓存、频繁文件追加写入这三个核心问题共同作用的结果,和算法逻辑错误关系不大,但资源管理和操作效率有明显优化空间:

一、核心原因分析

  • SharePoint对象内存泄漏:SharePoint的SPSite、SPWeb属于非托管资源,必须手动调用Dispose()释放。你的脚本仅在循环结束后调用了$site.Dispose(),但循环内每个$site以及$site.RootWeb都没释放,随着处理的站点增多,内存占用持续上升,GC(垃圾回收)压力增大,导致后期操作卡顿。
  • AD查询无缓存:每个符合*ozon.ru的用户都会触发两次AD查询(查用户、查经理),没有缓存已查询过的用户信息,后期重复查询累积,加上AD服务器的响应延迟,拖慢整体速度。
  • 频繁文件追加写入:每个数据库处理完就执行一次Export-Csv -Append和Export-Excel -Append,随着CSV/Excel文件体积增大,每次打开、写入、关闭文件的耗时会越来越长。

二、针对性优化方案

1. 严格释放SharePoint对象

使用try/finally块确保每个SPSite和SPWeb都被释放,彻底避免内存泄漏:

foreach ($site in $database.Sites) {
    try {
        # 处理站点逻辑
        $rootWeb = $site.RootWeb
        try {
            # 使用$rootWeb的业务逻辑,比如获取站点标题
        }
        finally {
            if ($rootWeb) { $rootWeb.Dispose() }
        }
    }
    finally {
        $site.Dispose()
    }
}

2. 缓存AD查询结果

用哈希表缓存已查询的用户和经理信息,避免重复调用Get-AdUser,同时将模糊匹配改为精确匹配提升查询效率:

# 初始化缓存哈希表
$adUserCache = @{}
$adManagerCache = @{}

# 查询用户时优先读取缓存
if (($ownerEmail -ne '') -and ($ownerEmail -like '*ozon.ru')) {
    if (-not $adUserCache.ContainsKey($ownerEmail)) {
        $ozon = Get-AdUser -Server "ozon" -Filter { userPrincipalName -eq $ownerEmail } -Properties employeeType, manager
        $adUserCache[$ownerEmail] = $ozon
    }
    else {
        $ozon = $adUserCache[$ownerEmail]
    }

    if ($ozon.manager -ne $null) {
        if (-not $adManagerCache.ContainsKey($ozon.manager)) {
            $managerObj = Get-AdUser -Server "ozon" -Filter { distinguishedName -eq $ozon.manager }
            $adManagerCache[$ozon.manager] = $managerObj.userPrincipalName
        }
        $manager = $adManagerCache[$ozon.manager]
    }
}

3. 减少文件写入次数

将所有站点信息汇总到全局集合,最后一次性写入文件,避免频繁的文件IO操作:

# 全局集合存储所有站点数据
$allSiteInfo = [System.Collections.ArrayList]::new()

# 循环内仅向集合添加数据
$allSiteInfo.Add([PSCustomObject]@{ ... }) | Out-Null

# 所有数据库处理完成后统一写入
$allSiteInfo | Export-Csv -Path "C:\SysOps\SharePointSites.csv" -NoTypeInformation -Encoding UTF8 -Delimiter ';'
$allSiteInfo | Export-Excel -Path "C:\SysOps\SharePointSites.xlsx"

4. 其他细节优化

  • 提前赋值重复使用的变量,比如$ownerEmail = $site.Owner.Email,避免多次调用属性。
  • 初始化$QuotaUsage为0,避免未赋值导致的异常。
  • 减少不必要的Write-Host输出,控制台IO操作也会累积耗时,可改为写入日志文件或仅输出关键进度。

三、优化后的完整脚本示例

Add-PSSnapin "Microsoft.SharePoint.PowerShell"

Measure-Command {
    $databases = Get-SPContentDatabase -WebApplication 'https://...'
    $emptyDB = 0
    $fullDB = 0
    $totalSites = 0
    $allSiteInfo = [System.Collections.ArrayList]::new()
    $adUserCache = @{}
    $adManagerCache = @{}

    foreach ($database in $databases) {
        if ($database.Sites.Count -eq 0) {
            Write-Host 'Database ' $database.Name ' is empty' -ForegroundColor Red
            $emptyDB++
        }
        else {
            Write-Host 'Database ' $database.Name ' has ' $database.Sites.Count ' sites' -ForegroundColor Green
            $fullDB++

            foreach ($site in $database.Sites) {
                try {
                    if ($site.Url.Contains("sitemaster")) {
                        Write-Host $site.Url "was excluded from export" -ForegroundColor Yellow    
                        continue
                    }

                    $ownerEmail = $site.Owner.Email
                    $siteUrl = $site.Url
                    $siteOwner = if ($ownerEmail -ne '') { $ownerEmail } else { $site.Owner.DisplayName }
                    $siteUsage = [math]::round($site.Usage.Storage / 1GB, 3)
                    $siteLastTimeUsage = $site.LastContentModifiedDate.ToString("yyyy-MM-dd")
                    $siteDatabases = $database.Name
                    $siteQuota = [math]::round($site.Quota.StorageMaximumLevel / 1GB, 3)
                    $QuotaUsage = 0
                    if ($siteQuota -ne 0) {
                        $QuotaUsage = [math]::round($siteUsage / $siteQuota * 100, 2)
                    }

                    $webTitle = $null
                    $rootWeb = $site.RootWeb
                    try {
                        $webTitle = $rootWeb.Title
                    }
                    finally {
                        if ($rootWeb) { $rootWeb.Dispose() }
                    }

                    $ozon = $null
                    $UserStatus = ''
                    $FiredDate = ''
                    $manager = ''
                    $ozonEnabled = $null

                    if (($ownerEmail -ne '') -and ($ownerEmail -like '*ozon.ru')) {
                        if (-not $adUserCache.ContainsKey($ownerEmail)) {
                            $ozon = Get-AdUser -Server "ozon" -Filter { userPrincipalName -eq $ownerEmail } -Properties employeeType, manager
                            $adUserCache[$ownerEmail] = $ozon
                        }
                        else {
                            $ozon = $adUserCache[$ownerEmail]
                        }

                        $ozonEnabled = $ozon.Enabled
                        if ($ozon.employeeType -like 'Fired*') {
                            $UserStatus = $ozon.employeeType.split()[0]
                            $FiredDate = [datetime]::parseexact($ozon.employeeType.split()[1], 'dd.MM.yyyy', $null).ToString('yyyy-MM-dd')
                        }
                        else {
                            $UserStatus = $ozon.employeeType
                        }

                        if ($ozon.manager -ne $null) {
                            if (-not $adManagerCache.ContainsKey($ozon.manager)) {
                                $managerObj = Get-AdUser -Server "ozon" -Filter { distinguishedName -eq $ozon.manager }
                                $adManagerCache[$ozon.manager] = $managerObj.userPrincipalName
                            }
                            $manager = $adManagerCache[$ozon.manager]
                        }
                    }

                    $allSiteInfo.Add([PSCustomObject]@{
                        WebTitle      = $webTitle
                        SiteURL       = $siteUrl
                        Owner         = $siteOwner
                        LastTimeUsage = $siteLastTimeUsage
                        DiskUsage     = $siteUsage
                        Quota         = $siteQuota
                        QuotaUsage    = $QuotaUsage
                        Database      = $siteDatabases
                        Ozon          = $ozonEnabled
                        UserStatus    = $UserStatus
                        FireDate      = $FiredDate
                        Manager       = $manager
                    }) | Out-Null
                }
                finally {
                    $site.Dispose()
                }
            }
        }
        $totalSites += $database.Sites.Count
    }

    # 统一写入文件
    $allSiteInfo | Export-Csv -Path "C:\SysOps\SharePointSites.csv" -NoTypeInformation -Encoding UTF8 -Delimiter ';'
    $allSiteInfo | Export-Excel -Path "C:\SysOps\SharePointSites.xlsx"

    Write-Host 'Empty databases - ' $emptyDB -ForegroundColor Red
    Write-Host 'Databases with sites - ' $fullDB -ForegroundColor Green 
    Write-Host '---'
    Write-Host 'Total sites - ' $totalSites -ForegroundColor Green
}

内容的提问来源于stack exchange,提问作者Evgeny Trifonov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 09:41:01