连接到一个Office 365组 - 编程方式 (二)

博客地址:http://blog.csdn.net/FoxDave
本文阐述通过编程方式连接到Office 365组的第二部分。

第三步:现代化我们的网站
批量连接组的过程包括两个步骤:

  • 准备并验证一个用来执行批量组连接过程的输入文件
  • 执行批量组连接过程

为批量组连接和验证创建一个输入文件
运行扫描器得到结果之后,我们就知道了哪些网站可以进行组连接。下一步就是准备一个CSV文件来执行批量组连接过程。CSV文件格式很简单:

  • URL列包含要进行组连接的网站集的URL。
  • Alias包含了我们想要使用的Office 365组的名称。注意不能包含空格并且之前没有被使用过。
  • IsPublic表示我们想让网站是公共的还是私有的。
  • Classification表示我们在进行组连接之后希望网站是哪个类别。我们需要设置它是因为将网站连接到一个组之后,类别就是在Office 365组级别进行维护了。

下面时一个简单的示例:

Url,Alias,IsPublic,Classification
https://contoso.sharepoint.com/sites/hrteam,hrteam,false,Medium Impact
https://contoso.sharepoint.com/sites/engineering,engineeringteam,true,Low Impact

为了帮助我们在使用文件之前进行验证,我们可以使用本部分结尾的PowerShell脚本。这个脚本会检查网站URL和组的名称。打开脚本将里面的管理中心URL更新成我们自己的然后执行它。脚本需要我们填写CSV文件的名称并在执行后生成一个报告。
在脚本执行过程中,可能会出现以下错误:

  • [ERROR] AzureAD Naming policy : PrefixSuffix does contain AD attributes that are resolved based on the user running the group-connection
    在Azure AD中,我们可以定义Office 365组的命名策略。如果策略包含了用户的AD属性,可能会造成问题,因为批量组连接操作时所有的网站都会使用当前用户。
  • [ERROR] AzureAD Creation policy : adminUPN is not part of group CanCreateGroupsId that controls Office 365 group creation
    如果Azure AD的组被限制为只有指定用户能够创建并且当前用户不具有权限的化,会发生失败。
  • [ERROR] siteUrl : Alias [siteAlias] contains a space, which is not allowed
    Office 365组的别名不能包含空格。
  • [ERROR] siteUrl : Classification [siteClassification] does not comply with available Azure AD classifications [ClassificationListString]
    提供的网站分类没有在Office组允许的网站分类中定义。
  • [ERROR] siteUrl : Alias [siteAlias] is in the Azure AD blocked word list [CustomBlockedWordsListString]
    如果在Azure AD中设置了屏蔽的单词列表并且提供的Office 365组名使用了其中的词就会发生这个错误。
  • [ERROR] siteUrl : Site is already connected to a group
    一个网站只能连接到单一的Office 365组,也就是说连接后的网站不能再次连接了。
  • [ERROR] siteUrl : Alias [siteAlias] is already in use
    每个Office 365组都需要一个唯一的别名,如果别名已经存在了就会发生这个错误。
  • [ERROR] siteUrl : Alias [siteAlias] was already marked as approved alias for another site in this file
    提供的网站别名已经在CSV文件中前面的部分被定义过了。
  • [ERROR] siteUrl : Site does not exist or is not available (status = site.Status)
    提供的网站URL不是可访问的网站集。

注意:将下面脚本中的变量$tenantAdminUrl更新为我们自己的租户管理中心URL。
在脚本执行过程中,会生成一个log文件,加上一个只包含错误的log文件的子集文件。

#region Logging and generic functions
function LogWrite
{
    param([string] $log , [string] $ForegroundColor)

    $global:strmWrtLog.writeLine($log)
    if([string]::IsNullOrEmpty($ForegroundColor))
    {
        Write-Host $log
    }
    else
    {    
        Write-Host $log -ForegroundColor $ForegroundColor
    }
}

function LogError
{
    param([string] $log)
    
    $global:strmWrtError.writeLine($log)
}

function IsGuid
{
    param([string] $owner)

    try
    {
        [GUID]$g = $owner
        $t = $g.GetType()
        return ($t.Name -eq "Guid")
    }
    catch
    {
        return $false
    }
}

function IsGroupConnected
{
    param([string] $owner)

    if (-not [string]::IsNullOrEmpty($owner))
    {
        if ($owner.Length -eq 38)
        {
            
            if ((IsGuid $owner.Substring(0, 36)) -and ($owner.Substring(36, 2) -eq "_o"))
            {
                return $true
            }
        }        
    }

    return $false
}

function ContainsADAttribute
{
    param($PrefixSuffix)

    $ADAttributes = @("[Department]", "[Company]", "[Office]", "[StateOrProvince]", "[CountryOrRegion]", "[Title]")


    foreach($attribute in $ADAttributes)
    {
        if ($PrefixSuffix -like "*$attribute*")
        {
            return $true
        }
    }

    return $false
}

#endregion

#######################################################
# MAIN section                                        #
#######################################################
# Tenant admin url
$tenantAdminUrl = "https://contoso-admin.sharepoint.com"
# If you use credential manager then specify the used credential manager entry, if left blank you'll be asked for a user/pwd
$credentialManagerCredentialToUse = ""

#region Setup Logging
$date = Get-Date
$logfile = ((Get-Item -Path ".\" -Verbose).FullName + "\GroupifyInputValidation_log_" + $date.ToFileTime() + ".txt")
$global:strmWrtLog=[System.IO.StreamWriter]$logfile
$global:Errorfile = ((Get-Item -Path ".\" -Verbose).FullName + "\GroupifyInputValidation_error_" + $date.ToFileTime() + ".txt")
$global:strmWrtError=[System.IO.StreamWriter]$Errorfile
#endregion

#region Load needed PowerShell modules
#Ensure PnP PowerShell is loaded
$minimumVersion = New-Object System.Version("2.24.1803.0")
if (-not (Get-InstalledModule -Name SharePointPnPPowerShellOnline -MinimumVersion $minimumVersion -ErrorAction Ignore)) 
{
    Install-Module SharePointPnPPowerShellOnline -MinimumVersion $minimumVersion -Scope CurrentUser -Force
}
Import-Module SharePointPnPPowerShellOnline -DisableNameChecking -MinimumVersion $minimumVersion
#endregion

#region Ensure Azure PowerShell is loaded
$minimumAzurePowerShellVersion = New-Object System.Version("2.0.0.137")
if (-not (Get-InstalledModule -Name AzureADPreview -MinimumVersion $minimumAzurePowerShellVersion -ErrorAction Ignore))
{
    install-module AzureADPreview -MinimumVersion $minimumAzurePowerShellVersion -Scope CurrentUser -Force
}

Import-Module AzureADPreview -MinimumVersion $minimumAzurePowerShellVersion

$siteURLFile = Read-Host -Prompt 'Input name of .CSV file to validate (e.g. sitecollections.csv) ?'

# Get the tenant admin credentials.
$credentials = $null
$adminUPN = $null
if(![String]::IsNullOrEmpty($credentialManagerCredentialToUse) -and (Get-PnPStoredCredential -Name $credentialManagerCredentialToUse) -ne $null)
{
    $adminUPN = (Get-PnPStoredCredential -Name $credentialManagerCredentialToUse).UserName
    $credentials = $credentialManagerCredentialToUse
    $azureADCredentials = Get-PnPStoredCredential -Name $credentialManagerCredentialToUse -Type PSCredential
}
else
{
    # Prompts for credentials, if not found in the Windows Credential Manager.
    $adminUPN = Read-Host -Prompt "Please enter admin UPN"
    $pass = Read-host -AsSecureString "Please enter admin password"
    $credentials = new-object management.automation.pscredential $adminUPN,$pass
    $azureADCredentials = $credentials
}

if($credentials -eq $null) 
{
    Write-Host "Error: No credentials supplied." -ForegroundColor Red
    exit 1
}
#endregion

#region Connect to SharePoint and Azure
# Get a tenant admin connection, will be reused in the remainder of the script
LogWrite "Connect to tenant admin site $tenantAdminUrl"
$tenantContext = Connect-PnPOnline -Url $tenantAdminUrl -Credentials $credentials -Verbose -ReturnConnection

LogWrite "Connect to Azure AD"
$azureUser = Connect-AzureAD -Credential $azureADCredentials
#endregion

#region Read Azure AD group settings
$groupSettings = (Get-AzureADDirectorySetting | Where-Object -Property DisplayName -Value "Group.Unified" -EQ)

$CheckGroupCreation = $false
$CanCreateGroupsId = $null
$CheckClassificationList = $false
$ClassificationList = $null
$CheckPrefixSuffix = $false
$PrefixSuffix = $null
$CheckDefaultClassification = $false
$DefaultClassification = $null
$CheckCustomBlockedWordsList = $false

if (-not ($groupSettings -eq $null))
{
    if (-not($groupSettings["EnableGroupCreation"] -eq $true))
    {
        # Group creation is restricted to a security group...verify if the current user is part of that group
        # See: https://support.office.com/en-us/article/manage-who-can-create-office-365-groups-4c46c8cb-17d0-44b5-9776-005fced8e618?ui=en-US&rs=en-001&ad=US
        $CheckGroupCreation = $true
        $CanCreateGroupsId = $groupSettings["GroupCreationAllowedGroupId"]
    }

    if (-not ($groupSettings["CustomBlockedWordsList"] -eq ""))
    {
        # Check for blocked words in group name
        # See: https://support.office.com/en-us/article/office-365-groups-naming-policy-6ceca4d3-cad1-4532-9f0f-d469dfbbb552?ui=en-US&rs=en-001&ad=US
        $CheckCustomBlockedWordsList = $true
        $option = [System.StringSplitOptions]::RemoveEmptyEntries
        $CustomBlockedWordsListString = $groupSettings["CustomBlockedWordsList"]
        $CustomBlockedWordsList = $groupSettings["CustomBlockedWordsList"].Split(",", $option)
        
        # Trim array elements
        [int] $arraycounter = 0
        foreach($c in $CustomBlockedWordsList)
        {
            $CustomBlockedWordsList[$arraycounter] = $c.Trim(" ")
            $arraycounter++
        }
    }

    if (-not ($groupSettings["PrefixSuffixNamingRequirement"] -eq ""))
    {
        # Check for prefix/suffix naming - any dynamic tokens beside [groupname] can be problematic since all 
        # groups are created using the user running the bulk groupify
        # See: https://support.office.com/en-us/article/office-365-groups-naming-policy-6ceca4d3-cad1-4532-9f0f-d469dfbbb552?ui=en-US&rs=en-001&ad=US
        $CheckPrefixSuffix = $true
        $PrefixSuffix = $groupSettings["PrefixSuffixNamingRequirement"]
    }

    if (-not ($groupSettings["ClassificationList"] -eq ""))
    {
        # Check for valid classification labels
        # See: https://support.office.com/en-us/article/Manage-Office-365-Groups-with-PowerShell-aeb669aa-1770-4537-9de2-a82ac11b0540 
        $CheckClassificationList = $true

        $option = [System.StringSplitOptions]::RemoveEmptyEntries
        $ClassificationListString = $groupSettings["ClassificationList"]
        $ClassificationList = $groupSettings["ClassificationList"].Split(",", $option)
        
        # Trim array elements
        [int] $arraycounter = 0
        foreach($c in $ClassificationList)
        {
            $ClassificationList[$arraycounter] = $c.Trim(" ")
            $arraycounter++
        }

        if (-not ($groupSettings["DefaultClassification"] -eq ""))
        {        
            $CheckDefaultClassification = $true
            $DefaultClassification = $groupSettings["DefaultClassification"].Trim(" ")
        }
    }    
}
#endregion

#region Validate input
LogWrite "General Azure AD validation"
if ($CheckPrefixSuffix -and (ContainsADAttribute $PrefixSuffix))
{
    $message = "[ERROR] AzureAD Naming policy : $PrefixSuffix does contain AD attributes that are resolved based on the user running the groupify"
    LogWrite $message Red
    LogError $message                         
}

if ($CheckGroupCreation)
{
    $groupToCheck = new-object Microsoft.Open.AzureAD.Model.GroupIdsForMembershipCheck
    $groupToCheck.GroupIds = $CanCreateGroupsId
    $accountToCheck = Get-AzureADUser -SearchString $adminUPN
    $groupsUserIsMemberOf = Select-AzureADGroupIdsUserIsMemberOf -ObjectId $accountToCheck.ObjectId -GroupIdsForMembershipCheck $groupToCheck
    if ($groupsUserIsMemberOf -eq $null)
    {
        $message = "[ERROR] AzureAD Creation policy : $adminUPN is not part of group $CanCreateGroupsId which controls Office 365 Group creation"
        LogWrite $message Red
        LogError $message                         
    }
}

# "approved" aliases
$approvedAliases = @{}

LogWrite "Validating rows in $siteURLFile..."
$csvRows = Import-Csv $siteURLFile

foreach($row in $csvRows)
{
    if($row.Url.Trim() -ne "")
    {
        $siteUrl = $row.Url
        $siteAlias = $row.Alias
        $siteClassification = $row.Classification
        if ($siteClassification -ne $null)
        {
            $siteClassification = $siteClassification.Trim(" ")
        }

        LogWrite "[VALIDATING] $siteUrl with alias [$siteAlias] and classification [$siteClassification]"

        try 
        {
            # First perform validations that do not require to load the site
            if ($siteAlias.IndexOf(" ") -gt 0)
            {
                $message = "[ERROR] $siteUrl : Alias [$siteAlias] contains a space, which not allowed"
                LogWrite $message Red
                LogError $message 
            }
            elseif (($CheckClassificationList -eq $true) -and (-not ($ClassificationList -contains $siteClassification)))
            {
                $message = "[ERROR] $siteUrl : Classification [$siteClassification] does not comply with available AzureAD classifications [$ClassificationListString]"
                LogWrite $message Red
                LogError $message                         
            }     
            elseif (($CheckCustomBlockedWordsList -eq $true) -and ($CustomBlockedWordsList -contains $siteAlias))
            {
                $message = "[ERROR] $siteUrl : Alias [$siteAlias] is in the AzureAD blocked word list [$CustomBlockedWordsListString]"
                LogWrite $message Red
                LogError $message                         
            }                       
            else 
            {
                # try getting the site
                $site = Get-PnPTenantSite -Url $siteUrl -Connection $tenantContext -ErrorAction Ignore
                
                if ($site.Status -eq "Active")
                {
                    if (IsGroupConnected $site.Owner)
                    {
                        $message = "[ERROR] $siteUrl : Site is already connected a group"
                        LogWrite $message Red
                        LogError $message 
                    }
                    else
                    {
                        $aliasIsUsed = Test-PnPOffice365GroupAliasIsUsed -Alias $siteAlias -Connection $tenantContext      
                        if ($aliasIsUsed)
                        {
                            $message = "[ERROR] $siteUrl : Alias [$siteAlias] is already in use"
                            LogWrite $message Red
                            LogError $message   
                        }
                        elseif ($approvedAliases.ContainsKey($siteAlias))
                        {
                            $message = "[ERROR] $siteUrl : Alias [$siteAlias] was already marked as approved alias for another site in this file"
                            LogWrite $message Red
                            LogError $message   
                        }
                        else 
                        {
                            $approvedAliases.Add($siteAlias, $siteAlias)
                            LogWrite "[VALIDATED] $siteUrl with alias [$siteAlias] and classification [$siteClassification]" Green
                        }                        
                    }
                }
                else 
                {
                    $message = "[ERROR] $siteUrl : Site does not exist or is not available (status = $($site.Status))"
                    LogWrite $message Red    
                    LogError $message
                }                
            }
        }
        catch [Exception]
        {
            $ErrorMessage = $_.Exception.Message
            LogWrite "Error: $ErrorMessage" Red
            LogError $ErrorMessage    
        }
        
    }
}
#endregion

#region Close log files
if ($global:strmWrtLog -ne $NULL)
{
    $global:strmWrtLog.Close()
    $global:strmWrtLog.Dispose()
}

if ($global:strmWrtError -ne $NULL)
{
    $global:strmWrtError.Close()
    $global:strmWrtError.Dispose()
}
#endregion

执行批量组连接过程

现在我们有了定义需要执行组连接操作的网站的输入文件,我们终于可以去执行了。下面的PowerShell脚本是一个示例,我们使用的时候需要做一些微调,因为相关的具体设置需求每个人可能是不一样的。

示例脚本执行了以下步骤:

  • 在必要时添加当前租户管理员作为网站管理员;组连接需要一个用户账户。
  • 验证网站模板/发布功能是否使用而影响组连接;跟扫描工具中的逻辑保持一致。
  • 确保没有阻碍现代化的功能被启用,如果有就修复一下。
  • 确保现代化页面功能时启用的。
  • 可选的:部署应用程序(例如,应用程序自定义)
  • 可选的:添加我们自己的现代化主页。
  • 调用组连接API。
  • 定义网站管理员和网站所有者作为组的所有者。
  • 定义网站成员作为组成员。
  • 从SharePoint管理中心移除添加的租户管理员和网站所有者。
  • 从Office 365组移除添加的租户管理员。

执行下面的PowerShell脚本需要我们更新租户管理中心的URL并且在运行时提供凭据和CSV输入文件。
注意:我们可以根据需要去更新/移除可选部分的脚本,或添加额外的任务现代化任务(如设置网站的主题)。同样地,还需要将$tenantAdminUrl更改为自己的。

#region Logging and generic functions
function LogWrite
{
    param([string] $log , [string] $ForegroundColor)

    $global:strmWrtLog.writeLine($log)
    if([string]::IsNullOrEmpty($ForegroundColor))
    {
        Write-Host $log
    }
    else
    {    
        Write-Host $log -ForegroundColor $ForegroundColor
    }
}

function LogError
{
    param([string] $log)
    
    $global:strmWrtError.writeLine($log)
}

function LoginNameToUPN
{
    param([string] $loginName)

    return $loginName.Replace("i:0#.f|membership|", "")
}

function AddToOffice365GroupOwnersMembers
{
    param($groupUserUpn, $groupId, [bool] $Owners)

    # Apply an incremental backoff strategy as after group creation the group is not immediately available on all Azure AD nodes resulting in resource not found errors
    # It can take up to a minute to get all Azure AD nodes in sync
    $retryCount = 5
    $retryAttempts = 0
    $backOffInterval = 2

    LogWrite "Attempting to add $groupUserUpn to group $groupId"  

    while($retryAttempts -le $retryCount)
    {
        try 
        {
            if ($Owners)
            {
                $azureUserId = Get-AzureADUser -ObjectId $groupUserUpn            
                Add-AzureADGroupOwner -ObjectId $groupId -RefObjectId $azureUserId.ObjectId  
                LogWrite "User $groupUserUpn added as group owner"  
            }
            else 
            {
                $azureUserId = Get-AzureADUser -ObjectId $groupUserUpn           
                Add-AzureADGroupMember -ObjectId $groupId -RefObjectId $azureUserId.ObjectId    
                LogWrite "User $groupUserUpn added as group member"  
            }
            
            $retryAttempts = $retryCount + 1;
        }
        catch 
        {
            if ($retryAttempts -lt $retryCount)
            {
                $retryAttempts = $retryAttempts + 1        
                Write-Host "Retry attempt number: $retryAttempts. Sleeping for $backOffInterval seconds..."
                Start-Sleep $backOffInterval
                $backOffInterval = $backOffInterval * 2
            }
            else
            {
                throw
            }
        }
    }
}

function UsageLog
{
    try 
    {
        $cc = Get-PnPContext
        $cc.Load($cc.Web)
        $cc.ClientTag = "SPDev:GroupifyPS"
        $cc.ExecuteQuery()
    }
    catch [Exception] { }
}
#endregion

function GroupifySite
{
    param([string] $siteCollectionUrl, 
          [string] $alias,
          [Boolean] $isPublic,
          [string] $siteClassification,
          $credentials,
          $tenantContext,
          [string] $adminUPN)
    
    
    #region Ensure access to the site collection, if needed promote the calling account to site collection admin
    # Check if we can access the site...if not let's 'promote' ourselves as site admin
    $adminClaim = "i:0#.f|membership|$adminUPN"    
    $adminWasAdded = $false
    $siteOwnersGroup = $null
    $siteContext = $null    
    $siteCollectionUrl = $siteCollectionUrl.TrimEnd("/");

    Try
    {
        LogWrite "User running groupify: $adminUPN"
        LogWrite "Connecting to site $siteCollectionUrl"
        $siteContext = Connect-PnPOnline -Url $siteCollectionUrl -Credentials $credentials -Verbose -ReturnConnection
    }
    Catch [Exception]
    {
        # If Access Denied then use tenant API to add current tenant admin user as site collection admin to the current site
        if ($_.Exception.Response.StatusCode -eq "Unauthorized")
        {
            LogWrite "Temporarily adding user $adminUPN as site collection admin"
            Set-PnPTenantSite -Url $siteCollectionUrl -Owners @($adminUPN) -Connection $tenantContext
            $adminWasAdded = $true
            LogWrite "Second attempt to connect to site $siteCollectionUrl"
            $siteContext = Connect-PnPOnline -Url $siteCollectionUrl -Credentials $credentials -Verbose -ReturnConnection
        }
        else 
        {
            $ErrorMessage = $_.Exception.Message
            LogWrite "Error for site $siteCollectionUrl : $ErrorMessage" Red
            LogError $ErrorMessage
            return              
        }
    }
    #endregion

    Try
    {
        # Groupify steps
        # - [Done] Add current tenant admin as site admin when needed
        # - [Done] Verify site template / publishing feature use and prevent groupify --> align with the logic in the scanner
        # - [Done] Ensure no modern blocking features are enabled...if so fix it
        # - [Done] Ensure the modern page feature is enabled
        # - [Done] Optional: Deploy applications (e.g. application customizer)
        # - [Done] Optional: Add modern home page
        # - [Done] Call groupify API
        # - [Done] Define Site Admins and Site owners as group owners
        # - [Done] Define Site members as group members
        # - []     Have option to "expand" site owners/members if needed
        # - [Done] Remove added tenant admin and site owners from SharePoint admins
        # - [Done] Remove added tenant admin from the Office 365 group

        #region Adding admin
        # Check if current tenant admin is part of the site collection admins, if not add the account        
        $siteAdmins = $null
        if ($adminWasAdded -eq $false)
        {
            try 
            {
                # Eat exceptions here...resulting $siteAdmins variable will be empty which will trigger the needed actions                
                $siteAdmins = Get-PnPSiteCollectionAdmin -Connection $siteContext -ErrorAction Ignore
            }
            catch [Exception] { }
            
            $adminNeedToBeAdded = $true
            foreach($admin in $siteAdmins)
            {
                if ($admin.LoginName -eq $adminClaim)
                {
                    $adminNeedToBeAdded = $false
                    break
                }
            }

            if ($adminNeedToBeAdded)
            {
                LogWrite "Temporarily adding user $adminUPN as site collection admin"
                Set-PnPTenantSite -Url $siteCollectionUrl -Owners @($adminUPN) -Connection $tenantContext
                $adminWasAdded = $true
            }
        }

        UsageLog
        #endregion

        #region Checking for "blockers"
        $publishingSiteFeature = Get-PnPFeature -Identity "F6924D36-2FA8-4F0B-B16D-06B7250180FA" -Scope Site -Connection $siteContext
        $publishingWebFeature = Get-PnPFeature -Identity "94C94CA6-B32F-4DA9-A9E3-1F3D343D7ECB" -Scope Web -Connection $siteContext

        if (($publishingSiteFeature.DefinitionId -ne $null) -or ($publishingWebFeature.DefinitionId -ne $null))
        {
            throw "Publishing feature enabled...can't groupify this site"
        }

        # Grab the web template and verify if it's a groupify blocker
        $web = Get-PnPWeb -Connection $siteContext -Includes WebTemplate,Configuration,Description
        $webTemplate = $web.WebTemplate + $web.Configuration

        if ($webTemplate -eq "BICENTERSITE#0" -or 
            $webTemplate -eq "BLANKINTERNET#0" -or
            $webTemplate -eq "ENTERWIKI#0" -or
            $webTemplate -eq "SRCHCEN#0" -or
            $webTemplate -eq "SRCHCENTERLITE#0" -or
            $webTemplate -eq "POINTPUBLISHINGHUB#0" -or
            $webTemplate -eq "POINTPUBLISHINGTOPIC#0" -or
            $siteCollectionUrl.EndsWith("/sites/contenttypehub"))
        {
            throw "Incompatible web template detected...can't groupify this site"
        }
        #endregion
        
        #region Enable full modern experience by enabling the pages features and disabling "blocking" features
        LogWrite "Enabling modern page feature, disabling modern list UI blocking features"
        # Enable modern page feature
        Enable-PnPFeature -Identity "B6917CB1-93A0-4B97-A84D-7CF49975D4EC" -Scope Web -Force -Connection $siteContext
        # Disable the modern list site level blocking feature
        Disable-PnPFeature -Identity "E3540C7D-6BEA-403C-A224-1A12EAFEE4C4" -Scope Site -Force -Connection $siteContext
        # Disable the modern list web level blocking feature
        Disable-PnPFeature -Identity "52E14B6F-B1BB-4969-B89B-C4FAA56745EF" -Scope Web -Force -Connection $siteContext
        #endregion

        #region Optional: Add SharePoint Framework customizations - sample
        LogWrite "Deploying SPFX application customizer"
        Add-PnPCustomAction -Name "Footer" -Title "Footer" -Location "ClientSideExtension.ApplicationCustomizer" -ClientSideComponentId "edbe7925-a83b-4d61-aabf-81219fdc1539" -ClientSideComponentProperties "{}"
        #endregion

        #region Optional: Add custom home page - sample
        LogWrite "Deploying a custom modern home page"
        $homePage = Get-PnPHomePage -Connection $siteContext
        $newHomePageName = $homePage.Substring($homePage.IndexOf("/") + 1).Replace(".aspx", "_new.aspx")
        $newHomePagePath = $homePage.Substring(0, $homePage.IndexOf("/") + 1)
        $newHomePage = Add-PnPClientSidePage -Name $newHomePageName -LayoutType Article -CommentsEnabled:$false -Publish:$true -Connection $siteContext

        # Add your additional web parts here!
        Add-PnPClientSidePageSection -Page $newHomePage -SectionTemplate OneColumn -Order 1 -Connection $siteContext
        Add-PnPClientSideText -Page $newHomePage -Text "Old home page was <a href=""$siteCollectionUrl/$homePage"">here</a>" -Section 1 -Column 1
        Set-PnPHomePage -RootFolderRelativeUrl ($newHomePagePath + $newHomePageName) -Connection $siteContext
        #endregion        

        #region Prepare for group permission configuration
        # Get admins again now that we've ensured our access
        $siteAdmins = Get-PnPSiteCollectionAdmin -Connection $siteContext
        # Get owners and members before the group claim gets added
        $siteOwnersGroup = Get-PnPGroup -AssociatedOwnerGroup -Connection $siteContext               
        $siteMembersGroup = Get-PnPGroup -AssociatedMemberGroup -Connection $siteContext               
        #endregion

        #region Call groupify API
        LogWrite "Call groupify API with following settings: Alias=$alias, IsPublic=$isPublic, Classification=$siteClassification"
        Add-PnPOffice365GroupToSite -Url $siteCollectionUrl -Alias $alias -DisplayName $alias -Description $web.Description -IsPublic:$isPublic -KeepOldHomePage:$true -Classification $siteClassification -Connection $siteContext
        #endregion

        #region Configure group permissions
        LogWrite "Adding site administrators and site owners to the Office 365 group owners"
        $groupOwners = @{}
        foreach($siteAdmin in $siteAdmins)
        {
            if (($siteAdmin.LoginName).StartsWith("i:0#.f|membership|"))
            {
                $siteAdminUPN = (LoginNameToUPN $siteAdmin.LoginName)
                if (-not ($siteAdminUPN -eq $adminUPN))
                {
                    if (-not ($groupOwners.ContainsKey($siteAdminUPN)))
                    {
                        $groupOwners.Add($siteAdminUPN, $siteAdminUPN)
                    }
                }
            }
            else 
            {
                #TODO: group expansion?    
            }
        }
        foreach($siteOwner in $siteOwnersGroup.Users)
        {
            if (($siteOwner.LoginName).StartsWith("i:0#.f|membership|"))
            {
                $siteOwnerUPN = (LoginNameToUPN $siteOwner.LoginName)
                if (-not ($groupOwners.ContainsKey($siteOwnerUPN)))
                {
                    $groupOwners.Add($siteOwnerUPN, $siteOwnerUPN)
                }
            }
            else 
            {
                #TODO: group expansion?    
            }
        }

        $site = Get-PnPSite -Includes GroupId -Connection $siteContext
        foreach($groupOwner in $groupOwners.keys)
        {
            try 
            {
                AddToOffice365GroupOwnersMembers $groupOwner ($site.GroupId) $true
            }
            catch [Exception]
            {
                $ErrorMessage = $_.Exception.Message
                LogWrite "Error adding user $groupOwner to group owners. Error: $ErrorMessage" Red
                LogError $ErrorMessage
            }
        }

        LogWrite "Adding site members to the Office 365 group members"
        $groupMembers = @{}
        foreach($siteMember in $siteMembersGroup.Users)
        {
            if (($siteMember.LoginName).StartsWith("i:0#.f|membership|"))
            {
                $siteMemberUPN = (LoginNameToUPN $siteMember.LoginName)
                if (-not ($groupMembers.ContainsKey($siteMemberUPN)))
                {
                    $groupMembers.Add($siteMemberUPN, $siteMemberUPN)
                }
            }
            else 
            {
                #TODO: group expansion?    
            }
        }

        foreach($groupMember in $groupMembers.keys)
        {
            try 
            {
                AddToOffice365GroupOwnersMembers $groupMember ($site.GroupId) $false                
            }
            catch [Exception]
            {
                $ErrorMessage = $_.Exception.Message
                LogWrite "Error adding user $groupMember to group members. Error: $ErrorMessage" Red
                LogError $ErrorMessage
            }
        }        
        #endregion

        #region Cleanup updated permissions
        LogWrite "Groupify is done, let's cleanup the configured permissions"
    
        # Remove the added site collection admin - obviously this needs to be the final step in the script :-)
        if ($adminWasAdded)
        {
            #Remove the added site admin from the Office 365 Group owners and members
            LogWrite "Remove $adminUPN from the Office 365 group owners and members"            
            $site = Get-PnPSite -Includes GroupId -Connection $siteContext
            $azureAddedAdminId = Get-AzureADUser -ObjectId $adminUPN
            try 
            {
                Remove-AzureADGroupOwner -ObjectId $site.GroupId -OwnerId $azureAddedAdminId.ObjectId -ErrorAction Ignore
                Remove-AzureADGroupMember -ObjectId $site.GroupId -MemberId $azureAddedAdminId.ObjectId -ErrorAction Ignore                    
            }
            catch [Exception] { }

            LogWrite "Remove $adminUPN from site collection administrators"            
            Remove-PnPSiteCollectionAdmin -Owners @($adminUPN) -Connection $siteContext
}
        #endregion

        LogWrite "Groupify done for site collection $siteCollectionUrl" Green
        
        # Disconnect PnP Powershell from site
        Disconnect-PnPOnline
    }
    Catch [Exception]
    {
        $ErrorMessage = $_.Exception.Message
        LogWrite "Error: $ErrorMessage" Red
        LogError $ErrorMessage

        #region Cleanup updated permissions on error
        # Groupify run did not complete...remove the added tenant admin to restore site permissions as final step in the cleanup
        if ($adminWasAdded)
        {
            # Below logic might fail if the error happened before the groupify API call, but errors are ignored
            $site = Get-PnPSite -Includes GroupId -Connection $siteContext
            $azureAddedAdminId = Get-AzureADUser -ObjectId $adminUPN
            try 
            {
                Remove-AzureADGroupOwner -ObjectId $site.GroupId -OwnerId $azureAddedAdminId.ObjectId -ErrorAction Ignore
                Remove-AzureADGroupMember -ObjectId $site.GroupId -MemberId $azureAddedAdminId.ObjectId -ErrorAction Ignore
                # Final step, remove the added site collection admin
                Remove-PnPSiteCollectionAdmin -Owners @($adminUPN) -Connection $siteContext
            }
            catch [Exception] { }
        }
        #endregion

        LogWrite "Groupify failed for site collection $siteCollectionUrl" Red
    } 

}

#######################################################
# MAIN section                                        #
#######################################################
# Tenant admin url
$tenantAdminUrl = "https://contoso-admin.sharepoint.com"
# If you use credential manager then specify the used credential manager entry, if left blank you'll be asked for a user/pwd
$credentialManagerCredentialToUse = ""

#region Setup Logging
$date = Get-Date
$logfile = ((Get-Item -Path ".\" -Verbose).FullName + "\Groupify_log_" + $date.ToFileTime() + ".txt")
$global:strmWrtLog=[System.IO.StreamWriter]$logfile
$global:Errorfile = ((Get-Item -Path ".\" -Verbose).FullName + "\Groupify_error_" + $date.ToFileTime() + ".txt")
$global:strmWrtError=[System.IO.StreamWriter]$Errorfile
#endregion

#region Load needed PowerShell modules
# Ensure PnP PowerShell is loaded
$minimumVersion = New-Object System.Version("2.24.1803.0")
if (-not (Get-InstalledModule -Name SharePointPnPPowerShellOnline -MinimumVersion $minimumVersion -ErrorAction Ignore)) 
{
    Install-Module SharePointPnPPowerShellOnline -MinimumVersion $minimumVersion -Scope CurrentUser
}
Import-Module SharePointPnPPowerShellOnline -DisableNameChecking -MinimumVersion $minimumVersion

# Ensure Azure PowerShell is loaded
$loadAzurePreview = $false
if (-not (Get-Module -ListAvailable -Name AzureAD))
{
    # Maybe the preview AzureAD PowerShell is installed?
    if (-not (Get-Module -ListAvailable -Name AzureADPreview))
    {
        install-module azuread
    }
    else 
    {
        $loadAzurePreview = $true
    }
}

if ($loadAzurePreview)
{
    Import-Module AzureADPreview
}
else 
{
    Import-Module AzureAD   
}
#endregion

#region Gather Groupify run input
# Url of the site collection to remediate
$siteCollectionUrlToRemediate = ""
$siteAlias = ""
$siteIsPublic = $false

# Get the input information
$siteURLFile = Read-Host -Prompt 'Input either single site collection URL (e.g. https://contoso.sharepoint.com/sites/teamsite1) or name of .CSV file (e.g. sitecollections.csv) ?'
if (-not $siteURLFile.EndsWith(".csv"))
{
    $siteCollectionUrlToRemediate = $siteURLFile
    $siteAlias = Read-Host -Prompt 'Input the alias to be used to groupify this site ?'
    $siteIsPublicString = Read-Host -Prompt 'Will the created Office 365 group be a public group ? Enter True for public, False otherwise'
    $siteClassificationLabel = Read-Host -Prompt 'Classification label to use? Enter label or leave empty if not configured'
    try 
    {
        $siteIsPublic = [System.Convert]::ToBoolean($siteIsPublicString) 
    } 
    catch [FormatException]
    {
        $siteIsPublic = $false
    }
}

# Get the tenant admin credentials.
$credentials = $null
$azureADCredentials = $null
$adminUPN = $null
if(![String]::IsNullOrEmpty($credentialManagerCredentialToUse) -and (Get-PnPStoredCredential -Name $credentialManagerCredentialToUse) -ne $null)
{
    $adminUPN = (Get-PnPStoredCredential -Name $credentialManagerCredentialToUse).UserName
    $credentials = $credentialManagerCredentialToUse
    $azureADCredentials = Get-PnPStoredCredential -Name $credentialManagerCredentialToUse -Type PSCredential
}
else
{
    # Prompts for credentials, if not found in the Windows Credential Manager.
    $adminUPN = Read-Host -Prompt "Please enter admin UPN"
    $pass = Read-host -AsSecureString "Please enter admin password"
    $credentials = new-object management.automation.pscredential $adminUPN,$pass
    $azureADCredentials = $credentials
}

if($credentials -eq $null) 
{
    Write-Host "Error: No credentials supplied." -ForegroundColor Red
    exit 1
}
#endregion

#region Connect to SharePoint and Azure
# Get a tenant admin connection, will be reused in the remainder of the script
LogWrite "Connect to tenant admin site $tenantAdminUrl"
$tenantContext = Connect-PnPOnline -Url $tenantAdminUrl -Credentials $credentials -Verbose -ReturnConnection

LogWrite "Connect to Azure AD"
$azureUser = Connect-AzureAD -Credential $azureADCredentials
#endregion

#region Groupify the site(s)
if (-not $siteURLFile.EndsWith(".csv"))
{
    # Remediate the given site collection
    GroupifySite $siteCollectionUrlToRemediate $siteAlias $siteIsPublic $siteClassificationLabel $credentials $tenantContext $adminUPN
}
else 
{
    $csvRows = Import-Csv $siteURLFile
    
    foreach($row in $csvRows)
    {
        if($row.Url.Trim() -ne "")
        {
            $siteUrl = $row.Url
            $siteAlias = $row.Alias
            $siteIsPublicString = $row.IsPublic
    
            try 
            {
                $siteIsPublic = [System.Convert]::ToBoolean($siteIsPublicString) 
            } 
            catch [FormatException] 
            {
                $siteIsPublic = $false
            }    

            $siteClassification = $row.Classification
            if ($siteClassification -ne $null)
            {
                $siteClassification = $siteClassification.Trim(" ")
            }

            GroupifySite $siteUrl $siteAlias $siteIsPublic $siteClassification $credentials $tenantContext $adminUPN
        }
    }
}
#endregion

#region Close log files
if ($global:strmWrtLog -ne $NULL)
{
    $global:strmWrtLog.Close()
    $global:strmWrtLog.Dispose()
}

if ($global:strmWrtError -ne $NULL)
{
    $global:strmWrtError.Close()
    $global:strmWrtError.Dispose()
}
#endregion
上一篇:通俗易懂,jdk8-@FunctionalInterface:函数式接口


下一篇:当SAP云平台account的service Marke place里找不到Machine Learning服务该怎么办