Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Blog Technique de Romelard Fabrice

Les dernières Actualités de Romelard Fabrice (Alias fabrice69 ou F___) principalement autour des technologies Microsoft

Actualités

  • Toutes les actualités et informations sur les technologies Microsoft principalement autour de .NET et SQL Server

Archives

Office 365: Nettoyage des versions de List Item avant migration depuis SharePoint On Premise vers SharePoint Online

Pour ceux qui travaillent dans un projet de migration, un des cas critiques est l’utilisateur qui ne sait pas pourquoi, ni parfois comment, il a activé le versionning sans aucune limite.

On se retrouve alors avec des sites ou la seule liste en question peut occuper un volume très important sans aucun intérêt pour l’utilisateur (mais surtout très compliqué pour les outils de migration).

Dans mon exemple, les deux premiers fichiers excel occupent plus de 400MB, sachant que le fichier unitaire fait moins d’1MB:

image

Voici donc un petit script permettant de faire le ménage sur une liste spéficique en conservant les XX dernières versions:

function Delete-Version-History([string]$SPWebURL, [string]$SPListName, [int]$MaintainVersionNumber)
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null

    Write-Host " -------------------------------------------------------- "
    Write-Host "SPWeb URL to configure:", $SPWebURL  -foregroundcolor Red
    $site = new-object Microsoft.SharePoint.SPSite($SPWebURL)
    $web = $site.openweb()
    Write-Host "    >> SPWeb URL from Object:", $web.URL -foregroundcolor Green

    $SPlist = $web.Lists[$SPListName]
    Write-Host "    >> SPList to Configure:", $SPlist.Title -foregroundcolor Green
    Write-Host "    >> Total Item to check:", $SPlist.Items.Count -foregroundcolor Green

    foreach ($SPitem in $SPlist.Items)
    {
        Write-Host "       >>>>>> File Item:", $SPitem.Name, "- Version Total:", $SPItem.Versions.count -foregroundcolor Magenta
        $currentVersionsCount = $SPItem.Versions.count
        if($currentVersionsCount -gt $MaintainVersionNumber)
        {
            for($i=$currentVersionsCount-1; $i -gt $MaintainVersionNumber; $i--)
            {
                 $SPItem.versions[$i].delete()
                Write-Host "             >>>>>> Version:", $i, " Deleted"

                $SPlist.Update();
            }
        }
    }

$web.Dispose();
$site.Dispose();

}
 
cls
Delete-Version-History
http://MyWebApplication/sites/MySPSite/MySPWeb "My List Name" 50

Une fois les paramètres fournis, le script permettra de supprimer toutes les versions précédent la valeur limite fournie.

image

Cette solution est intéressante, mais parfois on a besoin de configurer le versionning et le faire appliquer dans la liste, pour ceci, le plus simple est de passer par ce second script:

function Configure-Versionning-SPList([string]$SPWebURL, [string]$SPListName, [int]$MaintainMajorVersionNumber, [bool]$EnableMinorVersionYesNo, [int]$MaintainMinorVersionNumber)
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null

    Write-Host " -------------------------------------------------------- "
    Write-Host "SPWeb URL to configure:", $SPWebURL  -foregroundcolor Red
    $site = new-object Microsoft.SharePoint.SPSite($SPWebURL)
    $web = $site.openweb()
    Write-Host "    >> SPWeb URL from Object:", $web.URL -foregroundcolor Green

    $SPlist = $web.Lists[$SPListName]
    Write-Host "    >> SPList to Configure:", $SPlist.Title -foregroundcolor Green
    if($SPlist.EnableVersioning -eq $true)
    {
        $SPlist.MajorVersionLimit = $MaintainMajorVersionNumber;
        if($EnableMinorVersionYesNo -eq $true)
        {
             $SPlist.EnableMinorVersions = $EnableMinorVersionYesNo
            $SPlist.MajorWithMinorVersionsLimit = $MaintainMinorVersionNumber;
        }
    }
    else
    {
        $SPlist.EnableVersioning = $True
        $SPlist.MajorVersionLimit = $MaintainMajorVersionNumber;
        if($EnableMinorVersionYesNo -eq $true)
        {
            $SPlist.EnableMinorVersions = $EnableMinorVersionYesNo
            $SPlist.MajorWithMinorVersionsLimit = $MaintainMinorVersionNumber;
        }
    }
    $SPlist.Update();
    $web.Dispose();
    $site.Dispose();
}

function Force-Apply-Versionning-OnItems([string]$SPWebURL, [string]$SPListName, [int]$MaintainVersionNumber)
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null

    Write-Host " -------------------------------------------------------- "
    Write-Host "SPWeb URL to configure:", $SPWebURL  -foregroundcolor Red
    $site = new-object Microsoft.SharePoint.SPSite($SPWebURL)
    $web = $site.openweb()
    Write-Host "    >> SPWeb URL from Object:", $web.URL -foregroundcolor Green

    $SPlist = $web.Lists[$SPListName]
    Write-Host "    >> SPList to Configure:", $SPlist.Title -foregroundcolor Green
    Write-Host "    >> Total Item to check:", $SPlist.Items.Count -foregroundcolor Green

    foreach ($SPitem in $SPlist.Items)
    {
        Write-Host "       >>>>>> File Item:", $SPitem.Name, "- Version Total:", $SPItem.Versions.count -foregroundcolor Magenta
        $currentVersionsCount = $SPItem.Versions.count
        if($currentVersionsCount -gt $MaintainVersionNumber)
        {
            $SPitem.SystemUpdate()
        }
     }
    $SPlist.Update();
   
    $web.Dispose();
    $site.Dispose();

}

Configure-Versionning-SPList   http://MyWebApplication/sites/MySPSite/MySPWeb "My List Name" 50  $false 0
Force-Apply-Versionning-OnItems 
http://MyWebApplication/sites/MySPSite/MySPWeb "My List Name" 50 

Quelque soit votre choix, le principe reste toujours de nettoyer avant de lancer l’exécution de migration.

Romelard Fabrice [MBA Risk Management]

Source:

Office 365: Comment supprimer des éléments de liste SharePoint Online via PowerShell

En cas de migration de contenu dans SharePoint Online, il peut être très utile de pouvoir nettoyer le résultat.

Le cas standard est une liste ayant un nombre d’élément important (voir allucinant: par exemple 200’000 items), qui est au dessus de toutes les limites imposées par Microsoft en dur:

  • Vue limitée à 2’000
  • Alerte de liste à 5’000
  • Blocage de liste à 20’000

Ainsi la solutione st de faire 2 migrations de cette même liste (ou plus suivant le découpage souhaité):

  • Premier chargement = Liste vivante ayant les XX derniers mois de contenu (par exemple l’année courante)
  • Second chargement = liste d’archive ayant l’intégralité du contenu
  • Chargements optionels = Si on souhaite faire un découpage par année

De ce fait tous les chargements qui devront être nettoyés après coup, demande la suppression d’un très grand nombre d’éléments (dans notre exemple, plus de 180’000), ce qui est simplement infaisable avec les méthodes visuelles.

Il faut donc en passer par les scripts et PowerShell + CSOM est parfait pour ce type de gestion.

Voici donc un script permettant le nettoyage basé sur la date limite, ce qui veut dire supprimer tous les élément avant la date du 4 Janvier 2017.

[string]$username = "YourTenantAccount@yourtenant.onmicrosoft.com"
[string]$PwdTXTPath = "C:\FOLDERTOSTOREPWD\ExportedPWD-$($username).txt"

[string]$SiteCollectionToClean = https://YourTenant.sharepoint.com/sites/YourSiteColl
[string]$ListToCleancontent = "YourListName"
[string]$ItemDateLimitToDelete = "2017-01-04"
[int]$ItemLimitNumber = 0
[int]$MyRowLimit = 500

$secureStringPwd = ConvertTo-SecureString -string (Get-Content $PwdTXTPath)
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $secureStringPwd

function Load-DLLandAssemblies
{
    [string]$defaultDLLPath = ""

    # Load assemblies to PowerShell session
    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.Runtime.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.Online.SharePoint.Client.Tenant.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

}

function QuickCleanAllListItems([string]$SiteURL, [string]$ListName, [int]$LimitItemNum)
{
    $sw = New-Object System.Diagnostics.StopWatch
    $sw.Start()

    $ctx=New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
    $ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
    $ctx.RequestTimeout = 1000000 # milliseconds

    Write-Host " ---------------------------------------------------------------------------------"
    $spoWeb = $ctx.Web
    $ctx.Load($spoWeb)
    $ctx.ExecuteQuery()

    $MyListToClean = $spoWeb.Lists.GetByTitle($ListName)
    $ctx.Load($MyListToClean)
    $ctx.ExecuteQuery()
   
    Write-Host "Site URL: ", $SiteURL
    Write-Host "List Name:", $MyListToClean.Title

    Write-Host "  Items Number before delete: ", $MyListToClean.ItemCount -foregroundcolor Yellow

    $query = New-Object Microsoft.SharePoint.Client.CamlQuery
    $query.DatesInUtc = $true
    $query.ViewXml = "<View><Query><Where><Eq><FieldRef Name='Created'/><Value Type='DateTime' IncludeTimeValue='FALSE'>$($ItemDateLimitToDelete)</Value></Eq></Where><OrderBy><FieldRef Name='Created'/></OrderBy></Query><RowLimit>1</RowLimit></View>"

    $itemLimitList = $MyListToClean.GetItems($query)
    $ctx.Load($itemLimitList)
    $ctx.ExecuteQuery()

    if($itemLimitList.Count -gt 0)
    {
        $ItemLimitNumber = $itemLimitList[$itemLimitList.Count-1].ID
        [int]$looplimitcount
        [int]$LoopRound = 0
        DO
        {
            Write-Host "    ========================================="
            Write-Host "    Deletion Round Number: ", $LoopRound -foregroundcolor Red

            $queryclear = New-Object Microsoft.SharePoint.Client.CamlQuery
            $queryclear.DatesInUtc = $true
            $queryclear.ViewXml = "<View><Query><Where><Lt><FieldRef Name='ID'/><Value Type='Counter'>$ItemLimitNumber</Value></Lt></Where><OrderBy><FieldRef Name='ID'/></OrderBy></Query><RowLimit>$MyRowLimit</RowLimit></View>"
            $items = $MyListToClean.GetItems($queryclear)
            $ctx.Load($items)
            $ctx.ExecuteQuery()
            $looplimitcount = $items.Count
            if ($items.Count -gt 0) 
            {
                for ($i=$items.Count-1; $i-ge0; $i--)
                {
                    Write-Host "       >> Item ID: ", $items[$i].ID, " ====>> Deleted" -foregroundcolor Magenta
                    $items[$i].DeleteObject() 
                }
                $ctx.ExecuteQuery()
                Write-Host "     ====>> Update Applied !!!"  -foregroundcolor Red
            }
            $LoopRound += 1
        } WHILE ( $looplimitcount -gt 0)

    }
    $sw.Stop()

    Write-Host " ---------------------------------------------------------------------------------"

    $ctx.Load($MyListToClean)
    $ctx.ExecuteQuery()
    Write-Host "Items total after deletion: ", $MyListToClean.ItemCount -foregroundcolor Yellow
    write-host "Items deleted in " $sw.Elapsed.ToString()

}

Load-DLLandAssemblies
QuickCleanAllListItems $SiteCollectionToClean $ListToCleancontent $ItemNumberLimitToDelete

Il ne vous reste plus qu’à adapter les critères de recherche si vous souhaitez nettoyer selon un autre filtre via la partie CAMLQuery.

Pour ma part, les 180’000 items ont été supprimés en quelques heures au lieu de plusieurs jours si on passe par les écrans de base.

Liens en rapport avec ce script:

Fabrice Romelard [MBA Risk Management]

Office 365: Script PowerShell pour assigner des droits Full Control à un groupe défini

Dans le cadre de la gestion de contenu SharePoint Online et surtout dans un contexte de migration, les droits “Site Collection Administrator” sont à proscrire pour les “Site Onwers”, il est bien préférable de mettre ces personnes dans le groupe d’origine “Site Owners”.

Le problème se présente rapidement avec les personnes jouant avec ces permissions et décidant de casser celles-ci jusqu’à la suppression de son propre groupe. C’est aussi dans un contexte de migration la réinitialisation de tout le contenu au niveau de ces permissions.

Ainsi ce script PowerShell utilise le module CSOM afin de boucler dans tous les sites et sous-sites afin de donner le droit “Full Control” au groupe voulu au niveau de chaque sous-site, mais aussi au niveau de chaque liste (en revanche je ne parcoure pas le niveau des items volontairement).

Dans le même temps, le script assigne l’adresse email spécifié dans les “Site access request” afin de déléguer la gestion de ces permissions au propriétaire du site.

[string]$username = "LoginAccount@tenant.onmicrosoft.com"
[string]$PwdTXTPath = "C:\\SECUREDPWD\ExportedPWD-$($username).txt"

[string]$SPOSiteCollectionURLToSet = “https://tenant.sharepoint.com/sites/MySiteCollection
[string]$RootSiteOwnerGroupToSet = "MySiteCollection Owners"
[string]$SiteOwnerEmailAdress = "SiteOnwerEmail@mycompany.com"
[boolean]$ChangeRequestAccessEmail = $false
[string]$RoleTypeToApply = "Administrator"

function Load-DLLandAssemblies
{
    [string]$defaultDLLPath = ""

    # Load assemblies to PowerShell session

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.Runtime.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.Online.SharePoint.Client.Tenant.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)
}

Function Invoke-LoadMethod() {
param(
   [Microsoft.SharePoint.Client.ClientObject]$Object = $(throw "Please provide a Client Object"),
   [string]$PropertyName
)
   $ctx = $Object.Context
   $load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load")
   $type = $Object.GetType()
   $clientLoad = $load.MakeGenericMethod($type)


   $Parameter = [System.Linq.Expressions.Expression]::Parameter(($type), $type.Name)
   $Expression = [System.Linq.Expressions.Expression]::Lambda(
            [System.Linq.Expressions.Expression]::Convert(
                [System.Linq.Expressions.Expression]::PropertyOrField($Parameter,$PropertyName),
                [System.Object]
            ),
            $($Parameter)
   )
   $ExpressionArray = [System.Array]::CreateInstance($Expression.GetType(), 1)
   $ExpressionArray.SetValue($Expression, 0)
   $clientLoad.Invoke($ctx,@($Object,$ExpressionArray))
}

Function Add-Group-As-FullPermission-InSPWeb()
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context,
        [string]$SPGroupName,
        [Microsoft.SharePoint.Client.Web]$SPWeb,
        [string]$RoleType
    )
    Write-Host " ---------------------------------------------------------"

    # get group/principal
    $Mygroups = $SPWeb.SiteGroups
    $MyRootWeb = $Context.Site.RootWeb
    $context.Load($MyRootWeb)
    $Context.Load($Mygroups)
    $Context.ExecuteQuery()
<#
    foreach($MyTempGroup in $Mygroups)
    { Write-Host "        --->>> Group In Group List: ", $MyTempGroup.Title }
#>
    $Mygroup = $Mygroups | where {$_.Title -eq $SPGroupName}   
    #$MyRoleType = [Microsoft.SharePoint.Client.RoleType]$Roletype
   
    $roleDefs = $SPWeb.RoleDefinitions
    $Context.Load($roleDefs)
    $Context.ExecuteQuery()
    $roleDef = $roleDefs | where {$_.RoleTypeKind -eq $Roletype}   
    Write-Host "        --- Role Definition: ", $roleDef.Name
    Write-Host "        --- Group Name: ", $Mygroup.Title, " - Original Name:", $SPGroupName
   
    try{
        $collRdb = new-object Microsoft.SharePoint.Client.RoleDefinitionBindingCollection($Context)
        $collRdb.Add($roleDef)
        $collRoleAssign = $SPWeb.RoleAssignments
        $rollAssign = $collRoleAssign.Add($Mygroup, $collRdb)
        $Context.ExecuteQuery()
        Write-Host "       >>>>> Permissions assigned successfully." -ForegroundColor Green
    }
    catch{
        write-host "       !!!!!!! info: $($_.Exception.Message)" -foregroundcolor red
    }

    Write-Host " ---------------------------------------------------------"
}

Function Add-Group-As-FullPermission-InSPList()
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context,
        [string]$SPGroupName,
        [Microsoft.SharePoint.Client.Web]$SPWeb,
        [Microsoft.SharePoint.Client.List]$SPList,
        [string]$RoleType
    )
    Write-Host "         ---------------------------------------------------------"

    # get group/principal
    $Mygroups = $SPWeb.SiteGroups
    $MyRootWeb = $Context.Site.RootWeb
    $context.Load($MyRootWeb)
    $Context.Load($Mygroups)
    $context.Load($SPList)
    $Context.ExecuteQuery()

<#
    foreach($MyTempGroup in $Mygroups)
    { Write-Host "        --->>> Group In Group List: ", $MyTempGroup.Title }
#>
    $Mygroup = $Mygroups | where {$_.Title -eq $SPGroupName}   
    #$MyRoleType = [Microsoft.SharePoint.Client.RoleType]$Roletype
   
    # get role definition
    $roleDefs = $SPWeb.RoleDefinitions
    $Context.Load($roleDefs)
    $Context.ExecuteQuery()
    $roleDef = $roleDefs | where {$_.RoleTypeKind -eq $Roletype}   
    Write-Host "        --- Role Definition: ", $roleDef.Name
    Write-Host "        --- Group Name: ", $Mygroup.Title, " - Original Name:", $SPGroupName
   
    try{
        $collRdb = new-object Microsoft.SharePoint.Client.RoleDefinitionBindingCollection($Context)
        $collRdb.Add($roleDef)
        $collRoleAssign = $SPList.RoleAssignments
        $rollAssign = $collRoleAssign.Add($Mygroup, $collRdb)
        $Context.ExecuteQuery()
        Write-Host "       >>>>> Permissions assigned successfully." -ForegroundColor Green
    }
    catch{
        write-host "       !!!!!!! info: $($_.Exception.Message)" -foregroundcolor red
    }

    Write-Host "         ---------------------------------------------------------"
}

function Check-Permission-InLists
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context,
        [Microsoft.SharePoint.Client.Web]$SPWeb
    )

    $Mylists = $SPWeb.Lists;
    $Context.Load($Mylists)
    #you can use one execute per multiple loads
    $Context.ExecuteQuery();
    Write-host " ---- CHECK IN LISTS --- "
    foreach($myList in $MyLists)
    {
        Write-host "    ==== List Name:", $mylist.Title
        if($mylist.hidden -eq $false)
        {
            Write-host "        ====> List Name not hidden:", $mylist.Title   -ForegroundColor Yellow
            Invoke-LoadMethod -Object $myList -PropertyName "HasUniqueRoleAssignments"
            $context.ExecuteQuery()
            if($myList.HasUniqueRoleAssignments)
            {
                Write-Host "           -->> List in the SPWeb:", $myList.Title  -ForegroundColor Yellow
                Write-Host "           -->> Has Unique Permissions:", $myList.HasUniqueRoleAssignments

                Add-Group-As-FullPermission-InSPList -Context $Context -SPGroupName $RootSiteOwnerGroupToSet -SPWeb $SPWeb -SPList $myList -RoleType $RoleTypeToApply

            }
        }
    }
}

function Get-SPOSubWebs
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context,
        [Microsoft.SharePoint.Client.Web]$RootWeb
    )
   
    $Webs = $RootWeb.Webs
    $Context.Load($Webs)
    $Context.ExecuteQuery()
    ForEach ($sWeb in $Webs)
    {
        Write-host " -------------------------------------------------------- "
        Write-host "   -->> SubSite:", $sWeb.URL -ForegroundColor green
        Invoke-LoadMethod -Object $sWeb -PropertyName "HasUniqueRoleAssignments"
        $context.ExecuteQuery()
        Write-Host "       -->> Has Unique Permissions:", $sWeb.HasUniqueRoleAssignments

        if($sWeb.HasUniqueRoleAssignments)
        {
            Invoke-LoadMethod -Object $sWeb -PropertyName "RequestAccessEmail"
            $context.ExecuteQuery()
            Write-Host "       -->> Request Access Email Before change:", $sWeb.RequestAccessEmail
            if(($ChangeRequestAccessEmail) -and ($sWeb.RequestAccessEmail -ne $SiteOwnerEmailAdress))
            {
                Write-Host "      ===->> Request Access Email to change"
                $sWeb.RequestAccessEmail = $SiteOwnerEmailAdress
                $sWeb.Update()
                $context.ExecuteQuery()
                Invoke-LoadMethod -Object $sWeb -PropertyName "RequestAccessEmail"
                $context.ExecuteQuery()
                Write-Host "   -->> Request Access Email After change:", $sWeb.RequestAccessEmail
            }

            Add-Group-As-FullPermission-InSPWeb -Context $Context -SPGroupName $RootSiteOwnerGroupToSet -SPWeb $sWeb -RoleType $RoleTypeToApply
        }

        Check-Permission-InLists -Context $Myctx -SPWeb $sWeb
        Get-SPOSubWebs -Context $Context -RootWeb $sWeb
    }
}

function Reset-Group-OwnerShip
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context,
       # [Microsoft.SharePoint.Client.Web]$SPWeb,
        [string]$GroupOwnerName
    )
    $MyRootWeb = $Context.Site.RootWeb
    $Mygroups = $MyRootWeb.SiteGroups
    $context.Load($MyRootWeb)
    $Context.Load($Mygroups)
    $Context.ExecuteQuery()

    foreach($MyTempGroup in $Mygroups)
    {
        Write-Host "        --->>> Group Name: ", $MyTempGroup.Title
        $ThegroupOwner = $Context.Web.SiteGroups.GetByName($GroupOwnerName);
        $MyTempGroup.Owner = $ThegroupOwner
        $MyTempGroup.Update()
        $Context.ExecuteQuery()   
    }
}

function SetGroupAsFullOwner([string]$MyRootWebURL)
{
    [bool]$CreateSGSDocLibList = $false
   
    $Myctx = New-Object Microsoft.SharePoint.Client.ClientContext($MyRootWebURL)
    $secureStringPwd = ConvertTo-SecureString -string (Get-Content $PwdTXTPath)
    $creds = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $secureStringPwd
    $Myctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
    $Myctx.RequestTimeout = 1000000 # milliseconds
    $MyspoRootweb = $Myctx.Web
    $Myctx.Load($MyspoRootweb)
    $Myctx.ExecuteQuery()

Write-Host " "
Write-Host " ---------------------------------------------------------"
Write-Host "  >>>> # Server Version:" $Myctx.ServerVersion " # <<<<<<" -ForegroundColor Green
Write-Host " ---------------------------------------------------------"
Write-Host " "

    Write-host " -------------------------------------------------------- "
    Write-host "   -->> RootSite:", $MyspoRootweb.URL -ForegroundColor green

    Reset-Group-OwnerShip -Context $Myctx -GroupOwnerName $RootSiteOwnerGroupToSet
    Invoke-LoadMethod -Object $MyspoRootweb -PropertyName "RequestAccessEmail"
    $Myctx.ExecuteQuery()
    Write-Host "   -->> Request Access Email Before change:", $MyspoRootweb.RequestAccessEmail
   
    if($MyspoRootweb.RequestAccessEmail -ne $SiteOwnerEmailAdress)
    {
        Write-Host "   ===->> Request Access Email to change"
        $MyspoRootweb.RequestAccessEmail = $SiteOwnerEmailAdress
        $MyspoRootweb.Update()
        $Myctx.ExecuteQuery()
        Invoke-LoadMethod -Object $MyspoRootweb -PropertyName "RequestAccessEmail"
        $Myctx.ExecuteQuery()
        Write-Host "   -->> Request Access Email After change:", $MyspoRootweb.RequestAccessEmail
    }

    Check-Permission-InLists -Context $Myctx -SPWeb $MyspoRootweb
   
    Get-SPOSubWebs -Context $Myctx -RootWeb $MyspoRootweb
}

cls
Load-DLLandAssemblies

SetGroupAsFullOwner $SPOSiteCollectionURLToSet

Ce script est utilisé, testé et validé depuis des mois et fonctionne parfaitement, avec un test max d’une collection ayant 2900 sous-sites.

Fabrice Romelard [MBA Risk Management]

Sites utilisés pour certaines parties:

SharePoint 20XX: Script PowerShell pour exporter en CSV toutes les listes d’une ferme pour auditer le contenu avant migration

Dans un précédent message, nous avons pu comprendre comment nettoyer notre ferme SharePoint des listes vides:

Maintenant que celle-ci est “propre”, il convient de faire un assessment des listes contenues dans ces sites, sous-sites, … pour avoir une vue d’ensemble de la complexité du contenu à migrer et pouvoir se préparer au mieux.

Ce script va donc exporter pour vous un fichier CSV qu’il vous conviendra de manipuler selon votre besoin (via Excel, SQL Server, …).

Il reste dans le même esprit que la solution que Benoit Jester a publié pour l’exportation des sites et sous-sites d’une ferme:

Vous avez très peu de chose à paramétrer pour exécuter ce script en tant qu’administrateur sur un serveur de la ferme SharePoint.

param(
    [Parameter(Mandatory=$False)]
     [bool]$displayMessages = $true,
    [Parameter(Mandatory=$False)]
     [string]$SMTPServer="",
    [Parameter(Mandatory=$False)]
     [string]$ToEmailAddress="",
    [Parameter(Mandatory=$False)]
     [string]$FromEmailAddress="",
    [Parameter(Mandatory=$False)]
     [string]$delimiter="|"
)

[array]$ExceptionListnames = "List Template Gallery", "Master Page Gallery", "Site Template Gallery", "Web Part Gallery", "User Information List", "Settings", "Galería de plantillas de sitios", "Galería de plantillas de listas", "Galerie de modèles de sites", "Galerie de modèles de listes"

[string]$FarmType = "YOUR INTRANET TEAMSITES"
[string]$FolderToPutCSVName = "C:\TEMP\"
[string]$CSVFileName = "Lists-Usage-YourTeamsites.CSV"
[string]$ColorToShow = "green"
[string]$MyListIndexedColumns = ""
$pattern = '[^a-zA-Z]'
[string]$ColumnTitleCleaned = ""

# Applies Read Access to the specified accounts for a web application
Function Add-UserPolicy([String]$url)
{
    Try
    {
        $webapp = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup("$url")
        $user = ([Security.Principal.WindowsIdentity]::GetCurrent()).Name
        $displayName = "Sites Usage Read Account"
        $perm = "Full Read"
       
        # If the web app is not Central Administration
        If ($webapp.IsAdministrationWebApplication -eq $false)
        {
            # If the web app is using Claims auth, change the user accounts to the proper syntax
            If ($webapp.UseClaimsAuthentication -eq $true)
            {$user = 'i:0#.w|'+$user}
            [Microsoft.SharePoint.Administration.SPPolicyCollection]$policies = $webapp.Policies
            [Microsoft.SharePoint.Administration.SPPolicy]$policy = $policies.Add($user, $displayName)
            [Microsoft.SharePoint.Administration.SPPolicyRole]$policyRole = $webapp.PolicyRoles | where {$_.Name -eq $perm}
            If ($policyRole -ne $null)
            {$policy.PolicyRoleBindings.Add($policyRole)}
            $webapp.Update()
            If($displayMessages)
            {Write-Host -ForegroundColor White " Read access applied for `"$user`" account to `"$url`""}
        }
    }
    Catch
    {
        $_
        Write-Warning "An error occurred applying Read access for `"$user`" account to `"$url`""
    }
}

# Load assemblies
Function Load-Assemblies
{
    Try
    {
        If ((Get-PsSnapin |?{$_.Name -eq "Microsoft.SharePoint.PowerShell"})-eq $null)
        {
            If($displayMessages)
            {
                Write-Host -ForegroundColor Green "-----------------------------------------"
                Write-Host -ForegroundColor Green " - Loading SharePoint Powershell Snapin -"
                Write-Host -ForegroundColor Green "-----------------------------------------"
            }
            Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction Stop | Out-Null
        }
    }
    Catch
    {
        If($displayMessages)
        {
            Write-Host -ForegroundColor Green "------------------------------------------"
            Write-Host -ForegroundColor Green " - Loading Microsoft.SharePoint Assembly -"
            Write-Host -ForegroundColor Green "------------------------------------------"
        }
        [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null
    }
}

# Send Email with log file as attachment
Function SendEmail($attachment)
{
    Try
    {
        If($displayMessages)
        {
            Write-Host -ForegroundColor White "--------------------------------------------------------------"
            Write-Host -ForegroundColor White " Sending Email to $ToEmailAddress with $attachment in attachment."
        }
        Send-MailMessage -To $ToEmailAddress -From $FromEmailAddress -Subject "Sites Usage - $env:COMPUTERNAME" -SmtpServer $SMTPServer -Attachments $attachment
       
        If($displayMessages)
        {Write-Host -ForegroundColor Green " Email sent successfully to $ToEmailAddress"}
    }
    Catch
    {Write-Warning $_}
}

$DateStarted = $(Get-date)

If($displayMessages)
{
    Write-Host -ForegroundColor Green "----------------------------------"
    Write-Host -ForegroundColor Green "- Script started on $DateStarted -"
    Write-Host -ForegroundColor Green "----------------------------------"
}
# Check Permission Level
If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{Write-Warning "You don't have Administrator rights to run this script."}
else
{
    If($SMTPServer)
    {
        If (!$ToEmailAddress -or !$FromEmailAddress)
        {
            Write-Warning "Please specify a 'ToEmailAddress' and a 'FromEmailAddress' parameter."
            Exit
        }
    }
    # Load assemblies
    Load-Assemblies

    # Local variables
    $sitesList = $null
    $sitesList = @()

    # Build structure
    $itemStructure = New-Object psobject
    $itemStructure | Add-Member -MemberType NoteProperty -Name "FarmType" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "SPWebAppURL" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "SPSiteCollURL" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "SPWebURL" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListTitle" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListGUID" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListCreated" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListLastModifiedDate" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListDefaultViewURL" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListTemplate" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListItemsNumber" -value ""
    $itemStructure | Add-Member -MemberType NoteProperty -Name "ListIndexedColumns" -value ""
   
    If($displayMessages)
    {
        Write-Host -ForegroundColor Green "-----------------------"
        Write-Host -ForegroundColor Green " - Scanning All Sites -"
        Write-Host -ForegroundColor Green "-----------------------"
    }
   
    # Browse sites
    $WebSrv = [microsoft.sharepoint.administration.spwebservice]::ContentService
    foreach ($webApp in $WebSrv.WebApplications)
    {
        foreach ($AltUrl in $webapp.AlternateUrls)
        {Add-UserPolicy $AltUrl.uri}
       
        foreach ($site in $WebApp.sites)
        {
            $rootweb = $site.RootWeb;
            foreach($web in $site.AllWebs)
            {
                foreach($MyList in $web.lists)
                {
                    if($ExceptionListnames -notcontains $MyList.Title)
                    {
                        $MyListIndexedColumns = ""
                        foreach($MySPField in $MyList.Fields)
                        {
                            if($MySPField.Indexed)
                            {
                                $ColumnTitleCleaned =  $MySPField.Title -replace $pattern, ''
                                $MyListIndexedColumns += $ColumnTitleCleaned +"["+ $MySPField.InternalName +"]; "
                            }
                        }

                        if($MyList.ItemCount -gt 5000)
                        {
                            $ColorToShow = "red"
                        }
                        else
                        {
                            $ColorToShow = "green"
                        }
                        If($displayMessages)
                        {
                            Write-Host " -------------------------------------------------- "
                            Write-Host "      List Name:", $MyList.Title, "- GUID:", $MyList.ID -foregroundcolor $ColorToShow
                            Write-Host "      >>SPWebURL URL:", $web.Url -foregroundcolor $ColorToShow
                            Write-Host "      >>Items Count:", $MyList.ItemCount -foregroundcolor $ColorToShow
                            Write-Host "      >>Created Date:", $MyList.Created -foregroundcolor $ColorToShow
                            Write-Host "      >>Last modified Date:", $MyList.LastItemModifiedDate -foregroundcolor $ColorToShow
                            Write-Host "      >>List Indexed Columns:", $MyListIndexedColumns -foregroundcolor $ColorToShow
                        }
                        # Build structure
                        $siteInfos = $itemStructure | Select-Object *;

                        $siteInfos.FarmType = $FarmType;
                        $siteInfos.SPWebAppURL = $web.Url.Split("/")[2];
                        $siteInfos.SPSiteCollURL = $site.URL;
                        $siteInfos.SPWebURL =  $web.Url;

                        $siteInfos.ListTitle = $MyList.Title;
                        $siteInfos.ListGUID = $MyList.ID;
                        $siteInfos.ListCreated = $MyList.Created.ToString('d');
                        $siteInfos.ListLastModifiedDate = $MyList.LastItemModifiedDate.ToString('d');
                        $siteInfos.ListDefaultViewURL = $MyList.DefaultViewUrl;
                        $siteInfos.ListTemplate = $MyList.BaseTemplate;
                        $siteInfos.ListItemsNumber = $MyList.ItemCount;
                        $siteInfos.ListIndexedColumns = $MyListIndexedColumns;
                       
                        $sitesList += $siteInfos;
                    }
                }
            }
        }
        $rootweb.Dispose()

    }
   
    # CSV Export
    If($displayMessages)
    {
        Write-Host -ForegroundColor Green "---------------"
        Write-Host -ForegroundColor Green " - CSV Export -"
        Write-Host -ForegroundColor Green "---------------"
    }
    $sitesList | Where-Object {$_} | Export-Csv -Delimiter "$delimiter" -Path $CSVFileName -notype
     If($displayMessages)
    {Write-Host "Export file $CSVFileName successfully generated."}
 
    # Export File To DBServer
    Copy-Item $CSVFileName $FolderToPutCSVName
   
    # Email notification
    If($SMTPServer)
    {SendEmail $CSVFileName}
   
    # End
    If($displayMessages)
    {
        Write-Host -ForegroundColor Green "---------------------------------"
        Write-Host -ForegroundColor Green "- Script started : $DateStarted -"
        Write-Host -ForegroundColor Green "- Script finished : $(Get-date) -"
        Write-Host -ForegroundColor Green "---------------------------------"
    }
    Exit
}

Ce script est planifié sur toutes mes fermes afin de remonter ces CSV dans une base SQL Server qui permet ensuite de tirer des informations vitales, comme les liste ayant plus de 5’000 mais surtout plus de 20’000 items afin de placer les indexes correctement avant import. En effet, au dessus de 20’000 items il est impossible de changer la structure d’une liste SharePoint Online.

Fabrice Romelard [MBA Risk Management]

Office 365: Ajouter un utilisateur ou groupe dans la liste des Site collection Administrator d’un site SharePoint Online via PowerShell et CSOM

L’outil natif d’Office 365 évolue dans le temps (comme toute la plateforme) et se comporte donc parfois de manière différente. En effet, depuis plusieurs semaines, les comptes ajoutés dans les Site Collections administrators changent, ce qui est pénible à gérer.

Voici un petit script PowerShell permettant d’ajouter les logins voulus dans ce groupe d’administrateurs de collection. Dans mon cas, je veux charger par défaut les “Company Administrators” et “SharePoint Administrators” (il faut juste donner le login associé au groupe voulu).


Via PowerShell SPO Admin

La premiere solution est la plus simple, en passant simplement par les commandes de base du module d’administration SharePoint Online:


Via PowerShell et CSOM

Cette fois le script est plus générique et peut être adapté à votre besoin très rapidement:


[string]$username = "AdminAccount@tenant.onmicrosoft.com"
[string]$PwdTXTPath = "D:\ExportedPWD-$($username).txt"
[string]$SPOSiteCollectionURLToSet =
https://tenant.sharepoint.com/sites/MyCollection

#c:0-.f|rolemanager|s-1-1-11-11111111-111111-111111-1111 - Company Administrator
[string]$CompanyAdministratorLogin = "c:0-.f|rolemanager|s-1-1-11-11111111-111111-111111-1111"

# c:0-.f|rolemanager|s-1-1-11-11111111-111111-111111-22222- SharePoint Service Administrator
[string]$SharePointServiceAdministratorLogin = "c:0-.f|rolemanager|s-1-1-11-11111111-111111-111111-22222"

function Load-DLLandAssemblies
{
    [string]$defaultDLLPath = ""

    # Load assemblies to PowerShell session

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.Runtime.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.Online.SharePoint.Client.Tenant.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)
}


Function Add-Group-In-SiteCollectionAdmin()
{
    Param(
        [Parameter(Mandatory=$true,Position=1)][Microsoft.SharePoint.Client.ClientContext]$Context,
        [Parameter(Mandatory=$true,Position=2)][string]$SPUserOrGroupLogin
    )
    Write-Host " ---------------------------------------------------------"

    $MyspUser = $Context.Web.EnsureUser($SPUserOrGroupLogin);
    $MyspUser.IsSiteAdmin = $true;
    $MyspUser.Update()
    $Context.Load($MyspUser)
    #send the request containing all operations to the server
    try{
        $context.executeQuery()
        write-host " >>> info: User or Group Name added in Site Collection admin: [$($MyspUser.Title)]" -foregroundcolor green
    }
    catch{
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    }

    Write-Host " ---------------------------------------------------------"
}

function SetGroupAsAdministrator([string]$MyRootWebURL)
{
    [bool]$CreateSGSDocLibList = $false
   
    $Myctx = New-Object Microsoft.SharePoint.Client.ClientContext($MyRootWebURL)
    $secureStringPwd = ConvertTo-SecureString -string (Get-Content $PwdTXTPath)
    $creds = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $secureStringPwd
    $Myctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
    $Myctx.RequestTimeout = 1000000 # milliseconds
    $MyspoRootweb = $Myctx.Web
    $Myctx.Load($MyspoRootweb)
    $Myctx.ExecuteQuery()

Write-Host " "
Write-Host " ---------------------------------------------------------"
Write-Host "  >>>> # Server Version:" $Myctx.ServerVersion " # <<<<<<" -ForegroundColor Green
Write-Host " ---------------------------------------------------------"
Write-Host " "

    Add-Group-In-SiteCollectionAdmin -Context $Myctx -SPUserOrGroupLogin $CompanyAdministratorLogin
    Add-Group-In-SiteCollectionAdmin -Context $Myctx -SPUserOrGroupLogin $SharePointServiceAdministratorLogin

}
cls
Load-DLLandAssemblies

SetGroupAsAdministrator $SPOSiteCollectionURLToSet

Cette fonction peut alors facilement être inclue dans votre site de post configuration pour vous garantir l’accès à cette collection de sites pour ces utilisateurs quelque soit le changement opéré par Microsoft au fil du temps.

Fabrice Romelard [MBA Risk Management]


Site utiles:

Office 365: Comment créer une document library qui utilise les ContentTypeHub avec PowerShell et CSOM

On a vu précédemment comment gérer le Content Type Hub dans un Tenant Office 365:

Cette solution permet donc de publier un ensemble de types de contenu à travers le tenant Office 365.

Mais comment utiliser avec PowerShell ce type de contenu en créant une liste documentaire ayant pour type de contenu ces CTHub ?

Nous allons voir dans cet exemple, un script PowerShell basé sur le composant CSOM qui permet les actions suivantes:

  • Créer une document library vide
  • Ajouter 3 types de contenu (à partir des GUID que je connais, Excel, PowerPoint et Word)
  • Sauvegarder cette document library dans les modèles de la collection de sites pour l’utiliser à tous les niveaux de la collection


Script PowerShell

[string]$username = "AdminAccount@tenant.onmicrosoft.com"
[string]$PwdTXTPath = "C:\SCRIPTSREPO\ExportedPWD-$($username).txt"
[string]$ListTemplateName = "List Template Gallery"

[string]$CompanyDocLibInternalName = "_SPOCTHub_COMPANY_Documents_"
[string]$CompanyDocLibTitle = "COMPANY Documents"
[string]$CompanyDocLibDescription = "COMPANY Documents Library with COMPANY Templates"

# Get needed information from end user
[string]$SiteCollectionURL =
https://tenant.sharepoint.com/sites/yoursitecoll
$secureStringPwd = ConvertTo-SecureString -string (Get-Content $PwdTXTPath)

function Load-DLLandAssemblies
{
    [string]$defaultDLLPath = ""

    # Load assemblies to PowerShell session

    $defaultDLLPath = "C:\SCRIPTSREPO\NUGET_CSOM\microsoft.sharepointonline.csom.16.1.6008.1200\lib\net40-full\Microsoft.SharePoint.Client.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\SCRIPTSREPO\NUGET_CSOM\microsoft.sharepointonline.csom.16.1.6008.1200\lib\net40-full\Microsoft.SharePoint.Client.Runtime.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)

    $defaultDLLPath = "C:\SCRIPTSREPO\NUGET_CSOM\microsoft.sharepointonline.csom.16.1.6008.1200\lib\net40-full\Microsoft.Online.SharePoint.Client.Tenant.dll"
    [System.Reflection.Assembly]::LoadFile($defaultDLLPath)
}

Function AddCompanyContentType()
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context
    )

<#
CT to add
Content Type name: Company Excel  - ID: 0x01010046DD666F4125264BBBFC7EA3596C1371
Content Type name: Company Word With Logo  - ID: 0x0101005D8D60776F8605419F84F7FF61E87E1A
Content Type name: Company PowerPoint  - ID: 0x010100DE0DA8C046A65F499A445B0B74C55DAD

CT to remove
Content Type name: Document  - ID: 0x0101
#>
    [Microsoft.SharePoint.Client.List]$list = $context.web.lists.GetByTitle($CompanyDocLibInternalName)

    $ctIDCompanyWord = "0x010100DE0DA8C046A65F499A445B0B74C55DAD"
    $ctCompanyWord = $context.web.contenttypes.getbyid($ctIDCompanyWord)
    $context.load($ctCompanyWord)

    $ctIDCompanyPPT = "0x0101005D8D60776F8605419F84F7FF61E87E1A"
    $ctCompanyPPT = $context.web.contenttypes.getbyid($ctIDCompanyPPT)
    $context.load($ctCompanyPPT)

    $ctIDCompanyExcel = "0x01010046DD666F4125264BBBFC7EA3596C1371"
    $ctCompanyExcel = $context.web.contenttypes.getbyid($ctIDCompanyExcel)
    $context.load($ctCompanyExcel)

    $context.load($list)
    $context.load($list.contenttypes)

    $context.executeQuery()
    $list.ContentTypesEnabled = $true

    $AddctCompanyWord = $list.ContentTypes.AddExistingContentType($ctCompanyWord)
    $list.update()
    $AddctCompanyPPT = $list.ContentTypes.AddExistingContentType($ctCompanyPPT)
    $list.update()
    $AddctCompanyexcel = $list.ContentTypes.AddExistingContentType($ctCompanyExcel)
    $list.update()

    write-host " >> info: Enabled multiple content types"
    #send the request containing all operations to the server
    try{
        $context.executeQuery()
        write-host "info: added the Company content types to the list" -foregroundcolor green
    }
    catch{
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    }

    $ctIDDocument = "0x0101"
    $ctNameDocument = "Document"
   
    $ctCollectionInCompanyDocLib = $list.contenttypes
    $context.load($ctCollectionInCompanyDocLib)
    $context.executeQuery()
   
    foreach($CompanyDocLibCT in $ctCollectionInCompanyDocLib)
    {
        if ($CompanyDocLibCT.Name -eq $ctNameDocument)
        {
            $CTCompanyDocLibToRemove = $true
            $ctDocument = $CompanyDocLibCT
            write-host "    >> CT Name In the Company DocLib Before removal:", $CompanyDocLibCT.Name, "- ID:" $CompanyDocLibCT.ID
            break
        }
    }
    if($CTCompanyDocLibToRemove -eq $true)
    {
        $ctDocument.DeleteObject();
        try{
            $context.executeQuery()
            write-host "info: Removal of Document CT to the list" -foregroundcolor green
        }
        catch{
            write-host "info: $($_.Exception.Message)" -foregroundcolor red
        }       
    }
   
    $list.Title = $CompanyDocLibTitle
    $list.OnQuickLaunch = $true
    $list.EnableVersioning = $true
    $list.update()   
    write-host " >> info: Update the DocLib name"
    try{
        $context.executeQuery()
        write-host "info: Update the listName to set normal name" -foregroundcolor green
    }
    catch{
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    }   
    $list.SaveAsTemplate("_Company_Documents_With_Template.stp","_Company Documents","Company Document with Templates",$false)
    write-host " >> info: Save List as Template"
    try{
        $context.executeQuery()
        write-host "info: Save List as Template" -foregroundcolor green
    }
    catch{
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    }       
}

Function Create-CompanyDocLib()
{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context
    )
    $CompanyDocLibTemplateType = 101

    $lci = New-Object Microsoft.SharePoint.Client.ListCreationInformation
    $lci.title = $CompanyDocLibInternalName
    $lci.description = $CompanyDocLibDescription
   
    $lci.TemplateType = $CompanyDocLibTemplateType
    $list = $context.web.lists.add($lci)
    $context.load($list)
    #send the request containing all operations to the server
    try{
        $context.executeQuery()
        write-host " >>> info: Company DocLib Created $($CompanylistTitle)" -foregroundcolor green
       
        AddCompanyContentType  -Context $Context

    }
    catch{
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    }

    Write-Host " ---------------------------------------------------------"
}


Function Create-CompanyDocumentsWithCTHub([string]$SitecollectionURLToUse)
{
    $ctx=New-Object Microsoft.SharePoint.Client.ClientContext($SitecollectionURLToUse)

    $creds = New-Object System.Management.Automation.PSCredential -ArgumentList ($Username, $secureStringPwd)
    $ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
    $ctx.RequestTimeout = 1000000 # milliseconds
    #$ctx.Load($ctx.Web)
    $spoweb = $ctx.Web
    $ctx.Load($spoweb)
    $ctx.ExecuteQuery()

      Write-Host
  Write-Host $ctx.Url -BackgroundColor White -ForegroundColor DarkGreen

Write-Host " "
Write-Host " ---------------------------------------------------------"
Write-Host "  >>>> # Server Version:" $ctx.ServerVersion " # <<<<<<" -ForegroundColor Green
Write-Host " ---------------------------------------------------------"
Write-Host " "

    Create-CompanyDocLib -Context $ctx
}

cls
Load-DLLandAssemblies

Create-CompanyDocumentsWithCTHub $SiteCollectionURL

 

Une fois le script modifié selon vos besoin, vous aurez dans la collection une nouvelle liste documentaire apparaissant dans le menu de navigation avec les Type de contenus choisis.Si ces content types ont aussi un document modèle associés, celui-ci s’ouvrira dans Office Online directement.


Liens utiles:


Conclusion

Il ne vous reste plus qu’à communiquer autour de cette nouvelle liste documentaire reprenant vos standards internes et disponible dans toute la collection avec la création du modèle de liste.

Fabrice Romelard [MBA Risk Management]

Office 365: Attention au volume utilisé par les fichiers de Thèmes de SharePoint Online

Dans le cadre de SharePoint Online, le système de gestion des thèmes est différent du point de vue utilisateur, mais reste plus ou moins similaire dans le fonctionnement interne.

Ainsi, lorsque le site owner choisi un look pour le design de son site, SharePoint va créer tout un ensemble de fichiers qui est placé en cache au niveau racine de la collection, dans une librarie technique “Theme Gallery” de “_Catalogs”.

Dans le cas d’une collection qui possède un très grand nombre de sous-sites, ce système peut devenir problématique au niveau de la consommation du quota alloué à cette même collection de sites.

Dans l’exemple suivant, j’ai dépassé les 2 GB (en ayant mis à jour que la moitié des 3’300 sous-sites):

image

Il faut donc surveiller ce type de paramètre dans la définition de votre quota sous peine de bloquer totalement l’usage de ce site.

Fabrice Romelard [MBA Risk Management]

Office 365: Utiliser le bouton Export to Excel depuis un teamsite SharePoint Online et avec le client Office 2007

La version Office 2007 est encore supportée pour quelques mois par Microsoft, et donc fonctionne toujours chez de très nombreux clients. En revanche, pour certaines parties, le fonctionnement est loin d’être optimal surtout dans un contexte Office 365.

En effet, il existe certains clients qui utilisent encore les licenses Office 2007 acquises tout en ayant des comptes Office 365 (E1 par exemple). Or le modèle de sécurité entre Office 2007 et SharePoint Online est très différent et ce client supporte mal une partie des fonctionnalités pourtant de base dans la plateforme.

Un utilisateur configuré avec Office 2007 qui souhaite cliquer sur le bouton “Export to Excel”:

Modern View Classical View
image image

Recevra bien tout le processus standard pour ouvrir Excel avec le fichier “IQY” (Excel Internet Query):

image image

Mais l’utilisateur aura toujours le même message d’erreur suivant:

  • You do not have the adequate permissions to modify the list. Changes to your data cannot be saved

image

Ce message provient du fait qu’Excel 2007 ne sait pas parler avec le moteur de sécurité pour les ouverture de fichier iQY. Il n’arrive donc pas à passer le blocage de sécurité et pense donc qu’'e c’est uen question de permission.

La solution est un peu “tordue”, mais fonctionne parfaitement, car le but est d’avoir un jeton de sécurité actif au niveau d’Excel lors de cet export.

Pour cela, il faut:

  • Créer un fichier Excel basique dans une DocLib du même site SharePoint, directement depuis Excel Online par exemple
image image
  • Ouvrir ce fichier Excel basique dans Excel 2007 depuis Excel Online (le lien direct pour l’ouverture ne fonctionne pas)
image image
  • A ce moment, Excel vous demande le mode d’ouverture souhaité, il faut prendre “Edit”

image

  • Excel vous demande alors d’entrer les credentials Office 365 pour enfin charger le fichier
image image
  • A ce moment, tout en gardant ce fichier ouvert, il faut aller dans le site SharePoint pour ouvrir la liste que vous souhaitez exporter pour enfin cliquer sur “Export to Excel”. Vous n’aurez plus qu’à sélectionner la feuille ou placer ce contenu

image

  • Pour finalement trouver le contenu de votre liste à l’écran que vous pouvez sauver ou manipuler selon votre besoin

image

La solution est expliquée ici:

Romelard Fabrice [MBA Risk Management]

Office 365: Forcer la réindexation des données dans une liste SharePoint Online

SharePoint Online est un des modules de la plateforme Office 365 qui est aussi largement partagée, et donc sur laquelle il faut faire très attention pour les listes avec un grand nombre de données.

Pour information, les limites sont définies selon 2 valeurs:

  • La première alerte est de 5’000, qui vous impose à définir et utiliser les colonnes indexées
  • La seconde réelle limite est de 20’000, qui bloque tout changement de paramètre de cette liste

Quoi qu’il en soit il arrive qu’une liste ne permette plus l’utilisation du petit composant de recherche de la liste (en mode classique):

image

Pour forcer la réindexation totale de cette liste lors du prochain passage du moteur, il faut aller dans les paramètres avancés de la liste et au milieu de cette page chercher le menu “Reindex List”:

image

En cliquant sur ce bouton la liste est alors flaggée et son contenu sera totalement rescruté par le moteur.

Cette option est bien sur à utiliser avec parcimonie, même si Microsoft pilote totalement le travail du Crawler, en revanche elle permet parfois de résoudre des situations étranges sur les résultats de recherche.

Fabrice Romelard [MBA Risk Management]

Office 365: La gestion des Attachments dans les listes riches de SharePoint Online

Lorsque les utilisateurs passe d’une version d’un logiciel vers une nouvelle, il existe de nombreux petits réglages à faire. Parmis ceux-ci, les habitudes prises par ces utilisateurs sont à gérer.

Les listes possédant des champs riches telles que les messages de blogs ou les “Discussion boards” avaient un comportement dans SharePoint 2007 permettant d’ajouter des fichiers joints directement à un message:

image

Cette option n’est plus visible dans SharePoint Online et les utilisateurs habitués à cette façon de procéder peuvent être “perdus”.

La solution est associée au redesign de SharePoint depuis 2010 avec le ruban qui a apporté pour ces champs un menu dédié aux insertions:

image

Dans ce menu “insert”, on retrouve la possibilité d’ajouter un fichier comme pièce jointe. En revanche, ce fichier n’est plus stocké dans la liste elle-même, mais bien dans une librairie documentaire choisie lors de l’upload:

image

Cela veut aussi dire que l’utilisateur doit avoir les permissions adaptées sur les DocLib pour ce faire.

Il ne vous reste plus qu’à communiquer cette solution à vos utilisateurs pour qu’ils s’habituent à ces nouvelles méthodes.

Liens associés:

Fabrice Romelard [MBA Risk Management]

Office 365: Message d’erreur sur SharePoint Online “The file [file name] is checked out or locked for editing by [username]”

Dans un cas particulier, un message d’erreur peut apparaître:

 

image

Celui-ci provient de l’ouverture du document en édition depuis Office Online (Word, Excel ou PowerPoint) mais pour lequel l’utilisateur a quité l’application prématurément ou par accident (IE coupé par exemple).

A ce moment, si l’utilisateur essaye de modifier le même document par une autre voie (Office desktop, mais aussi en changeant le nom du fichier), un message apparaîtra:

  • The file "[file name]" is checked out or locked for editing by "[username]"

Comme dans l’image ci-dessus.

La source est simplement qu’Office Online crée un lock sur le document lors de l’ouverture et ne le lache que si l’utilisateur le ferme correctement.

Dans le cas inverse, le lock agit comme un bloqueur temporaire dont il faut attendre la fin de vie, environ 10 minutes.

Vous trouvez plus d’informations:

Fabrice Romelard [MBA Risk Management]

Office 365 : Script PowerShell pour créer le mapping des utilisateurs dans ShareGate à partir de SharePoint 2007

sharegate-logo

Lors du travail de préparation à la migration des sites SharePoint On Premise vers Office 365 SharePoint Online, il y a plusieurs étapes à mener telles que:

Une fois ce travail effectué, le client de migration ShareGate vous permet de faire un mapping pour chaque utilisateur entre le monde NTLM On Premise et le monde Office 365 et Azure AD.

Pour ceci, l’option la plus simple reste le client lui même avec un module de recherche simple:

image

En revanche dès que le nombre d’utilisateurs est important, il devient impossible de faire ce type de mapping manuel. Le module d’import XML est la solution en respectant le format suivant:

<?xml version="1.0"?>
<UserAndGroupMappings xmlns:xsd="
http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Mappings>
    <Mapping>
      <Source AccountName="DOMAIN\xxxxx_yyyyyy1" DisplayName="Xxxxx Yyyyyy 1" PrincipalType="User" /> 
      <Destination AccountName="i:0#.f|membership|xxxxx.yyyyyy1@mydomain.com" DisplayName="Xxxxx Yyyyyy 1" PrincipalType="User" /> 
    </Mapping>
    <Mapping>
      <Source AccountName="DOMAIN\xxxxx_yyyyyy2" DisplayName="Xxxxx Yyyyyy 1" PrincipalType="User" /> 
      <Destination AccountName="i:0#.f|membership|xxxxx.yyyyyy2@mydomain.com" DisplayName="Xxxxx Yyyyyy 2" PrincipalType="User" /> 
    </Mapping>
  </Mappings>
</UserAndGroupMappings>

Ainsi, dans mon cas, j’ai la chance d’avoir une base de données de référence avec tous les utilisateurs existants et valides dans le domaine.

Le script PowerShell permet donc de:

  • Se connecter à la collection de sites
  • Obtenir la liste de tous les utilisateurs de la collection (SPUsers List)
  • Tester chaque utilisateur dans la base de données
  • Créer le bloc XML de mapping

Une fois terminé, le script crée le fichier XML avec l’extension acceptée par ShareGate.

[string]$ConnectionString = "Data Source=MySQLServer;Integrated Security=True;Initial Catalog=MyDatabase;uid=MyAccount;pwd=MyPassword"
[string]$ProfileTable = "MyInternalUserTable"
[bool]$Verbose = $false
[int]$GLOBAL:TotalUsersUpdated = 0

[int]$NumberRow = 0
[string]$CleanedDestination_DisplayName = ""
[string]$CleanedSource_DisplayName = ""

[string]$MappingFileExported = "C:\TEMP\ShareGate-Create-UserMapping-SiteCollection\_SGS-UserAndGroupMappings-$(get-date -f yyyyMMdd-HHmm).sgum"

function GetSQLToDataSet([string]$sql)
{
    if($Verbose){Write-Host "SQL: ", $sql}

    $sqlCon = New-Object Data.SqlClient.SqlConnection
    $sqlCon.ConnectionString = $ConnectionString
    $ds = new-object "System.Data.DataSet" "MyDataSet"
    $da = new-object "System.Data.SqlClient.SqlDataAdapter" ($sql, $sqlCon)
    $da.Fill($ds)
    Write-Host "   DataSet.Table Row Number:", $ds.Tables[0].Rows.Count

    $sqlCon.close()
    $sqlCon.dispose()
    $table = new-object system.data.datatable
    $table = $set.Tables[0]
    return $table
}

function GetDataTablewithExecuteReader ([string]$SQLQuery)
{
    if($Verbose){Write-Host "SQL: ", $SQLQuery}

    $Datatable = New-Object System.Data.DataTable
    $Connection = New-Object System.Data.SQLClient.SQLConnection
    $Connection.ConnectionString = $ConnectionString
    $Connection.Open()
    $Command = New-Object System.Data.SQLClient.SQLCommand
    $Command.Connection = $Connection
    $Command.CommandText = $SQLQuery
    $Reader = $Command.ExecuteReader()
    $Datatable.Load($Reader)
    $Connection.Close()
    Write-Host "   DataTable Row Number:", $Datatable.Rows.Count

    return $Datatable
}

function Get-All-SiteCollection-Users([string]$SiteCollectionURL)
{
    $Datatable = New-Object System.Data.DataTable
    $Connection = New-Object System.Data.SQLClient.SQLConnection
    $Command = New-Object System.Data.SQLClient.SQLCommand
    $Connection.ConnectionString = $ConnectionString
    [string]$SQLCommandScript = ''
    [string]$AccountMappingXML = ''
   
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null
    $site = new-object Microsoft.SharePoint.SPSite($SiteCollectionURL)
    $web = $site.openweb()

    #Debugging - show SiteCollectionURL
    write-host "SiteCollectionURL: ", $SiteCollectionURL
    Write-Output "SiteCollectionURL - $SiteCollectionURL"

    $siteCollUsers = $web.SiteUsers
    write-host "Users Items: ", $siteCollUsers.Count
    [string]$XMLFile = "<?xml version=""1.0""?> `n"
    $XMLFile += "<UserAndGroupMappings xmlns:xsd=""
http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""> `n  <Mappings> `n"

    foreach($MyUser in $siteCollUsers)
    {
        $NumberRow += 1
        if(($MyUser.LoginName.ToLower() -ne "sharepoint\system") -and ($MyUser.LoginName.ToLower() -ne "nt authority\authenticated users") -and ($MyUser.LoginName.ToLower() -ne "nt authority\local service"))
        {
            #Write-Host "  USER: ", $MyUser.LoginName
            $UserName = $MyUser.LoginName.ToLower()
            Write-Host " >> User Login: ", $MyUser.LoginName
           
            $SQLCommandScript = "SELECT USER_NTNAME AS Source_AccountName, USER_PREFERRED_NAME AS Source_DisplayName, CASE LEN(USER_EMAIL) WHEN 0 THEN 'i:0#.f|membership|'+ (RIGHT(USER_NTNAME,LEN(USER_NTNAME)-CHARINDEX('\',USER_NTNAME)) + '@yourtenant.onmicrosoft.com') ELSE 'i:0#.f|membership|'+ USER_EMAIL END AS Destination_AccountName, (USER_LAST_NAME +', '+ USER_FIRST_NAME +' ('+ USER_CITY +')') AS Destination_DisplayName FROM [metricsops].[dbo].[MOSSProfileDataToWSS] WHERE USER_NTNAME = '$($MyUser.LoginName.ToLower())' ORDER BY USER_NTNAME ASC"
            #Write-Host "SQL: ", $SQLCommandScript
           
            $Datatable.Clear()
            $Connection.Open()
            $Command.Connection = $Connection
            $Command.CommandText = $SQLCommandScript
            $Reader = $Command.ExecuteReader()
            $Datatable.Load($Reader)
            $Connection.Close()
            Write-Host "   DataTable Row Number:", $Datatable.Rows.Count
 
            if ($Datatable.Rows.Count -gt 0)
            {
                foreach($myUserDataMapping in $Datatable.Rows)
                {
                    $CleanedDestination_DisplayName =  $myUserDataMapping.Destination_DisplayName -replace "&", " "
                    $CleanedSource_DisplayName =  $myUserDataMapping.Source_DisplayName -replace "&", " "

                    Write-Host " "
                    Write-Host " ========================================================="
                    Write-Host "    Row Number:", $NumberRow, " of ", $siteCollUsers.count
                    Write-Host "    SharePoint Onpremise Account Name:", $myUserDataMapping.Source_AccountName
                    Write-Host "    SharePoint Onpremise Display Name:", $CleanedSource_DisplayName
                    Write-Host "    O365 Destination Account Name:", $myUserDataMapping.Destination_AccountName
                    Write-Host "    O365 Destination Display Name:", $CleanedDestination_DisplayName
                    Write-Host " ---------------------------------------------------------"
                   
                    $AccountMappingXML = "    <Mapping> `n"
                    $AccountMappingXML += "      <Source AccountName=""$($myUserDataMapping.Source_AccountName)"" DisplayName=""$($CleanedSource_DisplayName)"" PrincipalType=""User"" />  `n"
                    $AccountMappingXML += "      <Destination AccountName=""$($myUserDataMapping.Destination_AccountName)"" DisplayName=""$($CleanedDestination_DisplayName)"" PrincipalType=""User"" />  `n"
                    $AccountMappingXML += "    </Mapping> `n"

                    $XMLFile += $AccountMappingXML
                }
            }
        }
    }

    $GLOBAL:TotalUsersUpdated += 1
    $XMLFile += "  </Mappings> `n </UserAndGroupMappings>"
    $XMLFile >> $MappingFileExported

    $web.Dispose()
    $site.Dispose()
}

function StartProcess()
{
    # Create the stopwatch
    [System.Diagnostics.Stopwatch] $sw;
    $sw = New-Object System.Diagnostics.StopWatch
    $sw.Start()
    #cls

    Get-All-SiteCollection-Users http://MySharePoint2007WebApp/sites/MySitecollection

    $sw.Stop()

    # Write the compact output to the screen
    write-host "Time: ", $sw.Elapsed.ToString()
}

StartProcess

Il vous faudra juste adapter le script pour le test des utilisateurs selon votre propre configuration.

Fabrice Romelard [MBA Risk Management]

SharePoint 2007: Script PowerShell pour nettoyer les listes vides avant migration vers Office 365 - Updated

Le contexte est toujours le même:

  • la préparation d’une migration

En effet, lorsqu’on arrive à ce stade, il y a toujours deux situations qui sont distinctes:

  • Upgrade des environnements On Premise vers les nouvelles versions (et donc DB Upgrade)
  • Passage d’environnement On Premise vers le Cloud (Office 365)

La première option implique donc la conservation de la structure et donc une certaine largesse dans la préparation, car on reste toujours dans une zone connue et une liste de plus ou de moins ne change pas grand chose.

La seconde option est plus discutable, car quelque soit l’outil choisi, le transfert reviendra à faire un import des données vers le tenant et donc tout ce que cela implique:

  • Temps de transfert
  • Utilisation du réseau
  • Temps de configuration et création des listes par l’outil
  • Temps pour appliquer les paramètres et les permissions

De ce fait, plus on réduit le nombre de liste plus on accélère la migration potentiel.

Un exemple rapide d’une ferme hypothétique avec la configuration suivante:

  • 100 collections de sites
  • 4 sous-sites par collection
  • 5 listes vides en moyenne (calendrier, contacts, annonces, …)
  • une moyenne de 20KB par liste

On obtient très rapidement la valeur suivante:

  • Nombre de listes vides à migrer: 100 * 4 * 5 = 2000
  • Taille totale inutile pour ces listes: 2000 * 20KB = 40 MB

Le temps pour créer une liste vide avec ShareGate par exemple est d’environ 30 secondes, ce qui vous donne un temps perdu:

  • 2000 * 30 = 60 000 secondes = env. 16 heures utilisées pour rien

Pour palier à cette situation, voici un script PowerShell à adapter selon votre besoin et exécuter sur chaque Web Application:

[int]$LimitMaxNumber = 5000
[int]$NumberMonthsOlder = -18 #(around 18 months before)

[boolean]$RemoveBasicEmptyLists = $true

[array]$ExceptionListnames = "List Template Gallery", "Master Page Gallery", "Site Template Gallery", "Web Part Gallery", "User Information List", "Settings", "Galería de plantillas de sitios", "Galería de plantillas de listas", "Galerie de modèles de sites", "Galerie de modèles de listes", "Galeria de Modelos de Site", "Galeria de Modelos de Lista"

[array]$BasicListToDelete = "Shared Documents", "Announcements", "Calendar", "Links", "Tasks", "Team Discussion", "Events", "General Discussion", "Contacts", "Discusión de grupo", "Tareas", "Documentos compartidos", "Calendario", "Eventos", "Contactos", "Discusión general", "Vínculos", "Anuncios", "Avisos", "Tarefas", "Documentos Compartilhados", "Contatos", "Discussão Geral", "Calendário", "Discussão em Equipe", "Eventos", "Discussion générale", "Tâches", "Événements", "Documents partagés", "Annonces", "Contacts", "Liens", "Calendrier", "Discussion d'équipe", "Извещения", "Контакты", "События", "Общие обсуждения", "Ссылки", "Ankündigungen", "Teamdiskussion", "Hyperlinks", "Freigegebene Dokumente", "Aufgaben", "Ankündigungen", "Kalender", "Documenti condivisi","Annunci", "Attività", "Calendario", "Collegamenti", "Discussione team", "Dokumenty udostępnione", "Anonsy", "Kalendarz", "Łącza", "Zadania", "Dyskusja zespołu", "共享文件", "宣告", "工作", "行事曆", "連結", "小組討論", "共有ドキュメント", "お知らせ", "タスク", "リンク", "予定表", "チーム ディスカッション", "공유 문서", "공지 사항", "링크", "일정", "작업", "팀 토론"

[array]$AnnouncementListNames = "Announcements", "Anuncios", "Annonces", "Ankündigungen", "Annunci", "Anonsy", "Извещения", "Avisos", "宣告", "お知らせ", "공지 사항"

[array]$AnnouncementDefaultItemValue = "Get Started with Windows SharePoint Services!", "¡Introducción a Windows SharePoint Services!", "Introducción a Windows SharePoint Services", "Guide de mise en route de Windows SharePoint Services", "Erste Schritte mit Windows SharePoint Services", "Introduzione a Windows SharePoint Services", "Rozpocznij pracę z programem Windows SharePoint Services!", "Начало работы с Windows SharePoint Services", "Bem-vindo ao Windows SharePoint Services!", "開始使用 Windows SharePoint Services!", "Windows SharePoint Services について", "Windows SharePoint Services를 시작합니다!"

function Check-Lists([string]$webURL)
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null
    $mysite = new-object Microsoft.SharePoint.SPSite($webURL)
    $myweb = $mysite.openweb()
    Write-Host "URL", $webURL
    #Write-Host "web", $myweb.Name
    Write-Host "Number of lists", $myweb.lists.count
    $ListsToDeleteFoSPWeb = New-Object System.Collections.ArrayList
   
    $LimitDate = (get-date).Addmonths($NumberMonthsOlder)
    Write-Host " >> Limit date for the lists -[", $LimitDate, "]"

    foreach($MyList in $myweb.lists)
    {
        if($ExceptionListnames -notcontains $MyList.Title)
        {
            if(($MyList.ItemCount -lt 1) -and ($MyList.LastItemModifiedDate -lt $LimitDate))
            {
                Write-Host "     ------------------------------------ "
                Write-Host "      List Name:", $MyList.Title, "- GUID:", $MyList.ID -foregroundcolor green
                Write-Host "      >>Items Count:", $MyList.ItemCount -foregroundcolor green
                Write-Host "      >>Last modified Date:", $MyList.LastItemModifiedDate -foregroundcolor green
                if($BasicListToDelete -contains $MyList.Title)
                {
                    Write-Host "       >>>>> List to delete: ", $MyList.Title, " <<<<<" -foregroundcolor green
                    $ListsToDeleteFoSPWeb += $MyList.ID
                }
            }
            else
            {
                if(($AnnouncementListNames -contains $MyList.Title) -and ($MyList.ItemCount -eq 1) -and ($MyList.LastItemModifiedDate -lt $LimitDate))
                {
                    if($AnnouncementDefaultItemValue -contains $MyList.Items[0].Title)
                    {
                        Write-Host "     ----------------------------------------------------- "
                        Write-Host "      Announcements Item[0] to delete:", $MyList.Items[0].Title -foregroundcolor green
                        Write-Host "       >>>>> List to delete: ", $MyList.Title, " <<<<<" -foregroundcolor green
                        Write-Host "     ----------------------------------------------------- "
                        $ListsToDeleteFoSPWeb += $MyList.ID
                    }
                    else
                    {
                        Write-Host "     ----------------------------------------------------- "
                        Write-Host "      Announcements Item[0] to check:", $MyList.Items[0].Title -foregroundcolor red
                        Write-Host "     ----------------------------------------------------- "
                    }
                }
                else
                {
                    if($MyList.ItemCount -gt 5000)
                    {
                        Write-Host "     ------------------------------------ "
                        Write-Host "      List Name:", $MyList.Title, "- GUID:", $MyList.ID -foregroundcolor magenta
                        Write-Host "      >>Items Count:", $MyList.ItemCount -foregroundcolor magenta
                    }
                    else
                    {
                        #Write-Host "      List Name:", $MyList.Title, "(", $MyList.ItemCount , Items") - GUID:", $MyList.ID  -foregroundcolor green $MyList.BaseTemplate
                    }
                }
            }
        }
    }

   
    if(($RemoveBasicEmptyLists) -and ($ListsToDeleteFoSPWeb.count -gt 0))
    {
        foreach($ToDeleteListGUID in $ListsToDeleteFoSPWeb)
        {
            Write-Host "      ======================================= "
            $myweb.Lists.Delete($ToDeleteListGUID)
            Write-Host "        >>> List GUID deleted:", $ToDeleteListGUID
            Write-Host "      ======================================= "
        }
    }
    foreach ($subweb in $myweb.GetSubwebsForCurrentUser())
    {
        Check-Lists $subweb.Url
    }
    $myweb.Dispose()
    $mysite.Dispose()
}

function Check-Large-Empty-Lists([string]$WebAppURL)
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null

    $Thesite = new-object Microsoft.SharePoint.SPSite($WebAppURL)
    $oApp = $Thesite.WebApplication

    foreach ($Sites in $oApp.Sites)
    {
        $mySubweb = $Sites.RootWeb
        Write-Host " -------------------------------------------------------- "
        #Write-Host "URL", $mySubweb.Url
        Check-Lists $mySubweb.Url
       
    }
    $Thesite.Dispose()

}

cls

Check-Large-Empty-Lists “http://YourSharePointSite2007”

En bonus, ce script vous affiche aussi en Magenta les listes qui contiennent plus qu’une valeur limite donnée dans le paramètre “$LimitMaxNumber”.

Le résultat d’exécution est le suivant:

image

Update:

J’ai ajouté la gestion de la liste “Announcement” qui contient de base 1 élément qui n’était pas donc vue comme vide. Cette fois je teste le cas de l’élément unique restant dans sa forme de base avec la valeur par défaut dans l’ensemble des langues (que j’avais à gérer).

Il ne vous reste ensuite plus qu’à faire un contrôle des sites avant migration pour voir si vous n’avez pas de site totalement vide (et donc à supprimer).

Fabrice Romelard [MBA Risk Management]

Office 365: Utiliser les alertes dans SharePoint Online

SharePoint Online permet d’utiliser les alertes via Email (ou SMS si votre tenant le permet) sur des actions se passant sur une liste ou librairie.

Pour effectuer cette mise en place, il faut aller dans la liste et suivant la vision que vous avez (moderne ou classique) vous pouvez cliquer sur “Set an Alert”.

Modern View

Dans la vision “Moderne” vous pouvez trouver le lien “Alert me” directement présent ou après les “…”, dépendant de votre niveau de permissions

SetAnAlertinSPO-02

Cette action ouvrira une fenêtre modale avec tous les paramètres à choisir selon votre souhait (nom, email, type d’action, …). Si vous avez les droits suffisants, vous pouvez aussi ajouter d’autres utilisateurs dans cette alerte créée.

image

Classic View

Dans la vue classique, vous avez l’onglet “Library” qui vous propose ensuite le bouton “Alert me” et l’action “Set Alert on this library”

SetAnAlertinSPO-01

La création de l’alerte est ensuite la même que pour la vue moderne.

Attention:

Si vous ou vos utilisateurs ne recevez pas ces alertes une fois paramétré et alors que des actions contrôlées se sont bien déroulées, il faut aller dans votre Outlook Online et vérifier les répertoires suivants:

  • Clutter
  • Junk Email

Fabrice Romelard [MBA Risk Management]

Windows: Comment comparer les outils de copie de fichiers disponibles en mode console avec un script PowerShell

Dans de nombreux cas, il est utile de pouvoir scripter ses copies de fichiers pour les intégrer dans une processus plus complèxe.

Ainsi, voici un petit script en exemple qui vous permet d’exécuter un test sur la copie d’un fichier de grande taille (10GB dans mon exemple d’un fichier MDF).

Ceci vous permet de faire une estimation sur l’outil le plus adapté à votre configuration et communiquer avec un temps estimé relativement valide.

#   ---- Start the File Move PowerShell Script ----
cls
[System.Diagnostics.Stopwatch] $sw;
$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()
Import-Module BitsTransfer
Start-BitsTransfer -Source "D:\FAKE_DATABASE.mdf" -Destination "H:\FAKE_DATABASE_BITS.mdf" -Description "Datafile move" -DisplayName "Datafile move"
write-host " >>>>BITS-Transfer Time: ", $sw.Elapsed.ToString() -foreground Red

$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()
cmd /c copy /z "D:\FAKE_DATABASE.mdf" "H:\FAKE_DATABASE_DOS.mdf"
write-host " >>>>DOS COPY Time: ", $sw.Elapsed.ToString() -foreground Red

$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()
robocopy "D:\" "H:\" "FAKE_DATABASE.mdf" /NP
write-host " >>>>ROBOCOPY Time: ", $sw.Elapsed.ToString() -foreground Red

$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()
xcopy "D:\FAKE_DATABASE.mdf" "H:\"  /E /I /Y
write-host " >>>>XCOPY Time: ", $sw.Elapsed.ToString() -foreground Red

$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()
Copy-Item "D:\FAKE_DATABASE.mdf" "H:\FAKE_DATABASE_PowerShellCOPY.mdf"
write-host " >>>>PowerShell Copy Time: ", $sw.Elapsed.ToString() -foreground Red

#   ---- End the File Move PowerShell Script ----

Une fois que vous executez ce fichier, vous aurez les temps d’exécution de chaque outil vous permettant de créer un tableau tel que:

image

Il ne vous reste plus que faire le calcul avec votre fichier de production pour avoir le temps estimé.

Attention, les vitesses de copie de fichier dépendent de très nombreux paramètres à tel point qu’il est difficile de dire lequel est le meilleur de manière générale.

Fabrice Romelard [MVP]

Office 365: Configurer l’ouverture des fichiers dans Office Web Apps pour une Librarie Documentaire

Lorsqu’on travaille avec Office 365 et surtout SharePoint Online, un des gros avantages est de pouvoir ouvrir les documents dans Office Web Apps (Word, Excel, PowerPoint, OneNote Online).

Tout ceci est parfait lorsqu’on crée une librarie documentaire depuis SharePoint Online et le paramètre est automatiquement appliqué pour suivre la configuration du tenant.


Visualisation du paramètre dans une librarie

  • [Votre Document Librarie] > Library Settings > Advanced Settings

image


Configuration manuelle par l’utilisateur

En revanche, si on utilise un outil de migration, le paramètre est remis avec une valeur autre dans de nombreux cas (notamment si vous passez de SharePoint 2007 à SPO via ShareGate):

image

Ainsi, il faut absolument reconfigurer les librairies après migration pour bénéficier des applications Online au niveau des documents.


Configuration via PowerShell pour SharePoint 2013 On Premise uniquement

Pour effectuer cette modification, il faut modifier la valeur du paramètre “DefaultItemOpen” et aussi “DefaultItemOpenUseListSetting” du modèle objet SharePoint 2013/2016

Les deux paramètres ont une logique simple:

  • Soit on veut appliquer le paramètre par défaut du serveur
    • DefaultItemOpenUseListSetting = $false
  • Soit on veut appliquer un paramètre spécifique pour la liste
    • DefaultItemOpenUseListSetting = $true
    • DefaultItemOpen = [Microsoft.Sharepoint.DefaultItemOpen]::PreferClient (pour ouverture avec Office desktop)
    • DefaultItemOpen = [Microsoft.Sharepoint.DefaultItemOpen]::Browser (pour ouverture avec Office Online)

La solution est expliquée ici:


Cas particulier de Office 365 SharePoint Online

SharePoint Online n’est accessible que via le modèle ClientContext qui est une version plus light que le modèle objet SharePoint 2013/2016.

Tous les paramètres ne sont pas disponibles dans ce client même avec la dernière version de SharePoint Online Management Shell [16.0.5625.1200].

L’objet “List” ne possède donc pas ces paramètres et il n’est pas possible de scripter cette modification.


Conclusion

Non seulement la plateforme Office 365 est très mouvante et changeante, mais les basiques n’y sont pas encore tous, tels que cette modification, pourtant plutôt simple en termes d’impact et intéressante pour les clients et Microsoft (Utiliser au mieux les applications Online).

Il reste à espérer que ce paramètre sera intégré dans une prochaine mise à jour du modèle objet Client Contexte.

Liens utiles:

Fabrice Romelard [MBA Risk Management]

Office 365: Comment gérer les Access Requests dans les sites SharePoint Online

Dans les sites SharePoint Online, il est possible de gérer les demande d’accès au niveau des sites, sous-sites ou listes.

Ce principe est basé sur une solution déjà éprouvée qui était expliquée ici:

Dans Office 365, le principe est très proche de la version 2013 avec le menu “Access Requests and invitations”:

image

Vous pouvez trouver toutes les demandes en attente (sachant que les Owners de sites peuvent gérer ces options):

image

 

Vous pouvez aussi aller dans les paramètres (Site Settings > Site permissions) et définir une adresse email pour recevoir les nouvelles demandes d’accès:

image clip_image008

C’est surtout une piqure de rappel pour déléguer cette gestion de permission au niveau des propriétaires de sites pour les responsabiliser sur ces accès.

Romelard Fabrice [MBA Risk Management]

Windows 2008 R2: Réduire la taille du répertoire system volume information

3513_Windows-Server-2008-R2-Logo-V_2652ACEF

Dans certains cas, il arrive que Windows Server 2008 R2 remplisse le disque système [C:\], et en regardant plus en détail, on trouve le folder “system volume information” prenant toute la place disponible:

image

La source de ce soucis est souvent liée au processus de Shadow Copy, qu’il faut reconfigurer correctement selon son besoin.

Le détail est dans cet échange:

De ce fait, le jeu de commandes est le suivant:

  • vssadmin list shadowstorage

Ce qui dans mon cas donne le résultat suivant

image

A partir de ce moment, il faut reconfigurer le disque avec une commande du type (que vous devez adapter à votre situation):

  • vssadmin resize shadowstorage /For=C: /On=C: /MaxSize=2GB

image

Cela aura pour effet de faire un clear du contenu et de forcer la limite à la valeur voulue.

On retrouve alors une situation plus normale sur la machine concernée.

image

Fabrice Romelard [MBA Risk Management]

Office 365: Comment éviter de taper son mot de passe du Tenant dans les scripts PowerShell

Lorsqu’on commence à gérer un Tenant Office 365, quelque soit le module à gérer, PowerShell est le seul outil à maîtriser absolument.

En revanche, il devient très rapidement pénible de taper le mot de passe du compte Office 365 à chaque exécution du script, et surtout impossible de scheduler un script avec ce concept. On ne va pas pour autant stocker son mot de passe en clair dans un fichier texte de la machine.

La solution est donc d’utiliser un script de génération de fichier avec le password crypté qui peut ensuite être utilisé dans son script

Voici donc un petit exemple de script:

[string]$username = "YourTenantAccount@yourtenant.onmicrosoft.com"
[string]$PwdTXTPath = "C:\FOLDERTOSTOREPWD\ExportedPWD-$($username).txt"

Write-Host " >> Account used:", $username -Foreground "Green"

Read-Host -Prompt "Please enter password:" -AsSecureString | ConvertFrom-SecureString | Out-File $PwdTXTPath

Write-Host " ---------------------------------------------- "
Write-Host " >> Your SecuredString file is stored in", $PwdTXTPath
Write-Host "    =>> You can use it in your script" -Foreground "Red"
Write-Host " ---------------------------------------------- "

Ce fichier qui sera stocké avec le path exact: “C:\FOLDERTOSTOREPWD\ExportedPWD-YourTenantAccount@yourtenant.onmicrosoft.com.txt” pourra être utilisé dans votre script de connexion comme suit:

[string]$username = "YourTenantAccount@yourtenant.onmicrosoft.com"
[string]$PwdTXTPath = "C:\FOLDERTOSTOREPWD\ExportedPWD-$($username).txt"

$secureStringPwd = ConvertTo-SecureString -string (Get-Content $PwdTXTPath)

$ctx=New-Object Microsoft.SharePoint.Client.ClientContext($Url)

$creds = New-Object System.Management.Automation.PSCredential -ArgumentList ($username , $secureStringPwd)
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($creds.UserName,$creds.Password)
$ctx.RequestTimeout = 1000000 # milliseconds
$spoweb = $ctx.Web
$ctx.Load($spoweb)
$ctx.ExecuteQuery()

Write-Host
Write-Host $ctx.Url -BackgroundColor White -ForegroundColor DarkGreen

Il ne vous reste plus qu’à mettre à jour ce fichier à chaque changement de mot de passe.

Liens utiles:

Romelard Fabrice [MBA Risk Management]

Office 365: Portal.office.com avec le compatibility View d’Internet Explorer 11

Microsoft a annoncé avec Windows 10 la fin du navigateur Internet Explorer, et de ce fait IE 11 est la dernière version.

C’est aussi le seul navigateur Microsoft possible sur l’OS Windows 7 (qui est encore sous support pour un moment). Nous avons d’ailleurs déjà eu des surprise avec une option de ce browser “Compatibility View Setting” avec SharePoint 2007 (SharePoint 2007 : Internet Explorer 10 incompatible avec certaines fonctionnalités).

Cette fois, ce paramètre joue des tours pour les utilisateurs Office 365 sous Windows ,  en ne présentant pas le même écran suivant sa configuration. D’ailleurs le même effet n’est pas toujours constaté sur Windows 10 (sans réelle explication).


Avec “Display intranet sites in Compatibility view” activé

image

On voit donc la page d’accueil Office 365 [https://portal.office.com] sans la barre de recherche et sans les fichiers proposés (au bas de la page).


Avec “Display intranet sites in Compatibility view” désactivé

image

La même page d’accueil présente maintenant la barre de recherche ainsi que les derniers documents accédés.


Windows 10 n’est pas aussi catégorique et sans pouvoir l’expliquer, le paramètre n’est pas toujours pris en compte, en revanche, c’est toujours vrai pour Windows 7.

De plus ce changement ne concerne que le navigateur Internet Explorer, pour les utilisateurs sous FireFox, Chrome ou autres, la barre de recherche est toujours présente.

Romelard Fabrice [MBA Risk Management]

Plus de Messages Page suivante »


Les 10 derniers blogs postés

- Office 365: Nettoyage des versions de List Item avant migration depuis SharePoint On Premise vers SharePoint Online par Blog Technique de Romelard Fabrice le 08-08-2017, 15:36

- Office 365: Comment supprimer des éléments de liste SharePoint Online via PowerShell par Blog Technique de Romelard Fabrice le 07-26-2017, 17:09

- Nouveau blog http://bugshunter.net par Blog de Jérémy Jeanson le 07-01-2017, 16:56

- Office 365: Script PowerShell pour assigner des droits Full Control à un groupe défini par Blog Technique de Romelard Fabrice le 04-30-2017, 09:22

- SharePoint 20XX: Script PowerShell pour exporter en CSV toutes les listes d’une ferme pour auditer le contenu avant migration par Blog Technique de Romelard Fabrice le 03-28-2017, 17:53

- Les pièges de l’installation de Visual Studio 2017 par Blog de Jérémy Jeanson le 03-24-2017, 13:05

- UWP or not UWP sur Visual Studio 2015 ? par Blog de Jérémy Jeanson le 03-08-2017, 19:12

- Désinstallation de .net Core RC1 Update 1 ou SDK de Core 1 Preview 2 par Blog de Jérémy Jeanson le 03-07-2017, 19:29

- Office 365: Ajouter un utilisateur ou groupe dans la liste des Site collection Administrator d’un site SharePoint Online via PowerShell et CSOM par Blog Technique de Romelard Fabrice le 02-24-2017, 18:52

- Office 365: Comment créer une document library qui utilise les ContentTypeHub avec PowerShell et CSOM par Blog Technique de Romelard Fabrice le 02-22-2017, 17:06