連線到一個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
相關文章
- 連線到一個Office 365組 - 程式設計方式 (一)程式設計
- 連線到一個Office 365組 - 配置方式
- Office 365組命名策略 - 概述
- Office 365組命名策略 - 補充
- 設計一個可靠的連線池
- Microsoft 365 for Mac(原Office 365)ROSMac
- Microsoft Graph for Office 365 - 查詢引數(二)ROS
- office365 developmentdev
- 一個二線城市程式設計師-週末一天的生活程式設計師
- office辦公套件:Microsoft 365 for Mac(原Office 365) v16.72套件ROSMac
- Microsoft Office 365 Mac版ROSMac
- 從連線到資料(二)
- Microsoft Graph for Office 365 - 查詢引數(一)ROS
- 一個程式設計師的連續套現程式設計師
- Microsoft Graph for Office 365概覽ROS
- [ Office 365 開發系列 ] 前言
- Microsoft 365 for Mac(Office365)16.78.3啟用版ROSMac
- java操作Oracle 方式一 ( 連線-》操作-》斷開連線 )JavaOracle
- office辦公套件:Microsoft 365 for Mac(原Office 365) v16.71啟用版套件ROSMac
- 一個簡單的反射連線程式反射線程
- ORACLE表連線方式及常見用法(二)Oracle
- 程式設計思路-球連球組成的群程式設計
- Microsoft 365 for Mac(Office 365啟用工具)正式啟用版ROSMac
- Office 365 API平臺概覽API
- [ Office 365 開發系列 ] Graph Service
- Microsoft Graph for Office 365 - 身份驗證路線圖和訪問令牌ROS
- Microsoft Graph for Office 365 - Azure AD應用程式許可權ROS
- Socket程式設計-長連線與短連線,心跳(keep-alive)程式設計Keep-Alive
- 從連線到資料(一)
- 從學生到程式設計師(二) (轉)程式設計師
- ORACLE 連線方式Oracle
- Oracle連線方式Oracle
- Arduino可穿戴教程之第一個程式——連線硬體選擇板子(二)UI
- 微軟改名部又出手:Office 365正在悄悄更名為Microsoft 365微軟ROS
- Microsoft 365 for Mac(Office 365)v16.80正式啟用版ROSMac
- Microsoft 365 for Mac(Office 365)v16.89正式啟用版ROSMac
- 假如全世界所有程式設計師組成一個王國程式設計師
- Microsoft Graph for Office 365請求語法ROS