none
PowerShell zum Durchsuchen eines SharePoints (REST, CSOM) RRS feed

  • Frage

  • Ich suche derzeit nach einer Möglichkeit, wie ich mit einem Script unseren Firmen-Sharepoint (O365 SharePoint Online) nach Dateien und Ordnern durchsuchen kann. Dies benötige ich als Grundlage für eine ganze Reihe von weiteren bereits existierenden Scripts, die nach dem vollständigen SharePoint-Umzug nicht mehr funktionieren werden, da diese über das FileSystemObject lediglich normale Netzlaufwerke durchsuchen können.

    Das ganze muss also idealerweise in PowerShell umgesetzt werden.


    Mein exemplarischer SharePoint ist Teil einer Collection und befindet sich unter "sites/collection3/sp1" (siehe unten). In dem Sharepoint haben die Fachabteilungen jeweils eine eigene Dokumentenbibliothek, bspw. "Production".


    Hierzu habe ich mich zum Thema REST API belesen und scheitere leider schon beim Einstieg in die Thematik...

    $TestQuery = Invoke-WebRequest -uri "https://mycompany.sharepoint.com/sites/collection3/sp1/Production" -Method Get -Credential "myemail@company.com"

    Ausgabe von $TestQuery:

    StatusCode        : 200
    StatusDescription : OK
    Content           : 
                        
                        <!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
                        <!DOCTYPE html>
                        <html dir="ltr" class="" lang="en">
                        <head>
                            <title>Sign in to your account</title>
                            <meta http-equiv="...
    RawContent        : HTTP/1.1 200 OK
                        Pragma: no-cache
                        Strict-Transport-Security: max-age=31536000; includeSubDomains
                        X-Content-Type-Options: nosniff
                        X-Frame-Options: DENY
                        Link: <https://aadcdn.msftauth.net>; rel=prec...
    Forms             : {}
    Headers           : {[Pragma, no-cache], [Strict-Transport-Security, max-age=31536000; includeSubDomains], [X-Content-Type-Options, nosniff], [X-Frame-Options, DENY]...}
    Images            : {}
    InputFields       : {}
    Links             : {@{innerHTML=Terms of use; innerText=Terms of use; outerHTML=<a class="footer-content ext-footer-content footer-item ext-footer-item" id="ftrTerms" href="https://www.microsoft.com/en-US/servicesagreement/" 
                        data-bind="&#10;            text: termsText,&#10;            href: termsLink,&#10;            click: termsLink_onClick,&#10;            externalCss: {&#10;                'footer-content': true,&#10;              
                          'footer-item': true,&#10;                'has-background': !useDefaultBackground,&#10;                'background-always-visible': hasDarkBackground }">Terms of use</a>; outerText=Terms of use; tagName=A; 
                        class=footer-content ext-footer-content footer-item ext-footer-item; id=ftrTerms; href=https://www.microsoft.com/en-US/servicesagreement/; data-bind=&#10;            text: termsText,&#10;            href: 
                        termsLink,&#10;            click: termsLink_onClick,&#10;            externalCss: {&#10;                'footer-content': true,&#10;                'footer-item': true,&#10;                'has-background': 
                        !useDefaultBackground,&#10;                'background-always-visible': hasDarkBackground }}, @{innerHTML=Privacy &amp; cookies; innerText=Privacy & cookies; outerHTML=<a class="footer-content ext-footer-content 
                        footer-item ext-footer-item" id="ftrPrivacy" href="https://privacy.microsoft.com/en-US/privacystatement" data-bind="&#10;            text: privacyText,&#10;            href: privacyLink,&#10;            click: 
                        privacyLink_onClick,&#10;            externalCss: {&#10;                'footer-content': true,&#10;                'footer-item': true,&#10;                'has-background': !useDefaultBackground,&#10;           
                             'background-always-visible': hasDarkBackground }">Privacy &amp; cookies</a>; outerText=Privacy & cookies; tagName=A; class=footer-content ext-footer-content footer-item ext-footer-item; id=ftrPrivacy; 
                        href=https://privacy.microsoft.com/en-US/privacystatement; data-bind=&#10;            text: privacyText,&#10;            href: privacyLink,&#10;            click: privacyLink_onClick,&#10;            externalCss: 
                        {&#10;                'footer-content': true,&#10;                'footer-item': true,&#10;                'has-background': !useDefaultBackground,&#10;                'background-always-visible': 
                        hasDarkBackground }}, @{innerHTML=...; innerText=...; outerHTML=<a class="footer-content ext-footer-content footer-item ext-footer-item debug-item ext-debug-item" id="moreOptions" role="button" 
                        aria-expanded="false" aria-label="Click here for troubleshooting information" href="#" data-bind="&#10;        click: moreInfo_onClick,&#10;        ariaLabel: str['CT_STR_More_Options_Ellipsis_AriaLabel'],&#10;   
                             attr: { 'aria-expanded': showDebugDetails().toString() },&#10;        hasFocusEx: focusMoreInfo(),&#10;        externalCss: {&#10;            'footer-content': true,&#10;            'footer-item': true,&#10; 
                                   'debug-item': true,&#10;            'has-background': !useDefaultBackground,&#10;            'background-always-visible': hasDarkBackground }">...</a>; outerText=...; tagName=A; class=footer-content 
                        ext-footer-content footer-item ext-footer-item debug-item ext-debug-item; id=moreOptions; role=button; aria-expanded=false; aria-label=Click here for troubleshooting information; href=#; data-bind=&#10;        
                        click: moreInfo_onClick,&#10;        ariaLabel: str['CT_STR_More_Options_Ellipsis_AriaLabel'],&#10;        attr: { 'aria-expanded': showDebugDetails().toString() },&#10;        hasFocusEx: focusMoreInfo(),&#10;   
                             externalCss: {&#10;            'footer-content': true,&#10;            'footer-item': true,&#10;            'debug-item': true,&#10;            'has-background': !useDefaultBackground,&#10;            
                        'background-always-visible': hasDarkBackground }}}
    ParsedHtml        : mshtml.HTMLDocumentClass
    RawContentLength  : 187698

    Was kann ich mit den gefundenen Daten jetzt anfangen? Wie kann ich daraus den Ordnerinhalt von "Production" herauslesen?

    EDIT: Thread umbenannt, da das Thema sich in eine andere Richtung entwickelt.
    • Bearbeitet Guido_T Montag, 8. März 2021 10:32 Thread umbenannt, da das Thema sich in eine andere Richtung entwickelt.
    Freitag, 5. März 2021 14:41

Antworten

  • Hi Guido,
    bau in den Catch scope einfach nur mal eine einfache Auswertung ein und zeige, was da in log.txt angezeigt wird:

        }catch {
            $_.Exception.Message | Out-File $prot -append
            if ($_.Exception.InnerException.Response){
                throw ([System.IO.StreamReader]($_.Exception.InnerException.Response.GetResponseStream())).ReadToEnd()
            }else{
                throw $_.Exception.InnerException
            }
        }

    Ich wette, dass die Url für die WebSite falsch ist:

    https://mycompany.sharepoint.com/sites/collection3/sp1/Production

    Wenn du diese Url im Browser eingibst, wird da das gewünschte Portal (WebSite) angezeigt?


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks




    Montag, 8. März 2021 12:21
  • Hi Guido,
    hier mal ein PowerShell Script mit CSOM. Die beiden erforderlichen dll habe ich in den Unterordner Cloud kopiert.

    <# 
    SharePoint via CSOM auslesen; alle Dateien anzeigen, incl. Ordner
    #> 
    
    Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Cloud\Microsoft.SharePoint.Client.dll"
    Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Cloud\Microsoft.SharePoint.Client.Runtime.dll"
    
    if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}
    
    # ====== Variablen =========
    $SiteUrl = "https://xxx.sharepoint.com"
    $Username = 'xxx@xxx.onmicrosoft.com'
    $Password = 'xxx'
    $prot = "c:\temp\log.txt"
    # ====== ENDE Variablen ====
    
    #Create Credential object from given user name and password
    $Cred = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, (ConvertTo-SecureString $Password -AsPlainText -Force))
        
    #Set up the context
    $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
    $Ctx.Credentials = $Cred
    
    function Portal()
    {
        try{
        # Get the Web Object
        $Web = $Ctx.web
        $Ctx.Load($Web)
        $Ctx.ExecuteQuery()
        "Portal: ServerRelativeUrl: " + $Web.ServerRelativeUrl + " - Title: " + $Web.Title | Out-File $prot -append
        # Lists / Bibs für Hauptportal
        ListsBibs($Web)
        # Subportale
        SubPortals($Web)
            }catch {
            $ex = $_.Exception
            while($ex -ne $null) {
                $ex.Message | Out-File $prot -append
                $ex = $ex.InnerException
            }
            throw $_
        }
    }
    
    function SubPortals([Microsoft.SharePoint.Client.Web]$web)
    {
        $Webs =$Web.Webs
        $ctx.Load($Webs)
        $Ctx.ExecuteQuery()
        ForEach ($item in $Webs) {
            "SubPortal: ServerRelativeUrl: " + $item.ServerRelativeUrl + " - Title: " + $item.Title | Out-File $prot -append
            ListsBibs($item)
            SubPortals($item)
        }
    }
    
    function ListsBibs([Microsoft.SharePoint.Client.Web]$web)
    {
        $Lists = $Web.Lists
        $ctx.Load($Lists)
        $Ctx.ExecuteQuery()
        # Listen / Bibliotheken
        ForEach ($item in $Lists) {
            if ($item.BaseTemplate -eq "101") {
                $listenName = $item.Title
                "  Liste/Bibliothek " + $item.BaseTemplate + " : " + $listenName + " - " + $Web.ServerRelativeUrl + "/" + $listenName | Out-File $prot -append
                BibFolders $item
            }
        }
    }
    
    function BibFolders([Microsoft.SharePoint.Client.List]$ListBib)
    {
        BibFiles $ListBib.RootFolder
        # Subfolders
        $Folders = $ListBib.RootFolder.Folders
        $ctx.Load($Folders)
        $Ctx.ExecuteQuery()
        ForEach ($item in $Folders) {
            "    Subfolder: " + $item.Name + " - " + $item.ServerRelativeUrl  | Out-File $prot -append
            BibFiles $item
            }
    #    }
    }
    
    function BibFiles([Microsoft.SharePoint.Client.Folder]$Folder)
    {
        $Files = $Folder.Files
        $ctx.Load($Files)
        $Ctx.ExecuteQuery()
        ForEach ($item in $Files) { "      Dokument: " + $item.Name | Out-File $prot -append }
    }
    
    "-------- Analyse" | Out-File $prot
    
    Portal


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Dienstag, 9. März 2021 07:54
  • Hi Guido,
    ich habe mir mal die Mühe gemacht, einen PowerShell Script zu schreiben, mit dem aus allen Dokument Bibliotheken incl. der Ordner und Unterordner die Dateien aufgelistet werden (in einer txt Datei), und das mittels REST Api.

    <# 
    SharePoint via API auslesen; alle Dateien anzeigen, incl. Ordner
    #> 
    
    # ====== Variablen =========
    $Uri = "http://sps2019.lg.loc"
    $USERNAME = 'lg\admin'
    $PASSWORD = 'Passw0rd'
    $Namespace = @{ns="http://www.w3.org/2005/Atom"; d="http://schemas.microsoft.com/ado/2007/08/dataservices"; m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"}
    $prot = "c:\temp\log.txt"
    # ====== ENDE Variablen ====
    
    if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}
    
    # Funktion zum Senden eines WebRequests
    function Execute-GetRequest {
        param(
            [String]$URL
        )
        try{
            # Parameter für Anmeldung
            $nc = [System.Net.NetworkCredential]::new($USERNAME,$PASSWORD)
            # Web-Zugriff
            $wr = [System.Net.WebRequest]::Create($Uri + $URL)
            $wr.Credentials = [System.Net.NetworkCredential]::new($USERNAME,$PASSWORD)
            $wr.Method      = 'GET'
            $wr.Accept      = 'application/xml;odata=verbose'
            $wr.ContentType = "application/xml;odata=verbose"
            # Antwort vorbereiten
            [System.Net.HttpWebResponse]$wresp = $wr.GetResponse()
            # Datenstrom holen
            $responseStream = $wresp.GetResponseStream()
            # Reader zuweisen
            $responseXML = [Xml]([System.IO.StreamReader]($responseStream)).ReadToEnd()
            $responseStream.Close()
            return $responseXML
        }catch {
            if ($_.Exception.InnerException.Response){
                throw ([System.IO.StreamReader]($_.Exception.InnerException.Response.GetResponseStream())).ReadToEnd()
            }else{
                throw $_.Exception.InnerException
            }
        }
    }
    
    function Portal([String]$reativeUrl = "/")
    {
        # root Portal
        $resultXML = Execute-GetRequest($reativeUrl + "/_api/web")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) {
            "Portal: ServerRelativeUrl: " + $item.Node.ServerRelativeUrl + " - Title: " + $item.Node.Title | Out-File $prot -append
            ListsBibs($item.Node.ServerRelativeUrl)
        }
        # Subportale
        $resultXML = Execute-GetRequest($reativeUrl + "/_api/site/rootWeb/webinfos")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) {
            "SubPortal: ServerRelativeUrl: " + $item.Node.ServerRelativeUrl + " - Title: " + $item.Node.Title | Out-File $prot -append
            ListsBibs($item.Node.ServerRelativeUrl)
        }
    }
    
    function ListsBibs([String]$relativeUrl)
    {
        $resultXML = Execute-GetRequest($relativeUrl + "/_api/web/lists")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) {
            if ($item.Node.BaseTemplate.'#text' -eq "101") {
                $listenName = $item.Node.Title
                $decodedBibName = [System.Xml.XmlConvert]::DecodeName($item.Node.EntityTypeName)
                "  Liste/Bibliothek " + $item.Node.BaseTemplate.'#text' + " : " + $listenName + " - " + $relativeUrl + "/" + $decodedBibName | Out-File $prot -append
                BibFolders $relativeUrl $decodedBibName 
            }
        }
    }
    
    function BibFolders
    {
        param([String]$relativeUrl, [String]$bibName)
        BibFiles $relativeUrl $bibName
        # Subfolders
        $resultXML2 = Execute-GetRequest($relativeUrl + "/_api/web/GetFolderByServerRelativeUrl('$bibName')/folders") 
        $result2 = Select-Xml -Xml $resultXML2 -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item2 in $result2) {
            "    Subfolder: " + $item2.Node.Name + " - " + $item2.Node.ServerRelativeUrl  | Out-File $prot -append
            $newBibName2 = $bibName + "/" + $item2.Node.Name
            BibFolders $relativeUrl $newBibName2
            }
    #    }
    }
    
    function BibFiles
    {
        param([String]$relativeUrl, [String]$bibName)
        $resultXML = Execute-GetRequest($relativeUrl + "/_api/web/GetFolderByServerRelativeUrl('$bibName')/Files")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) { "      Dokument: " + $item.Node.Name | Out-File $prot -append }
    }
    
    Portal "/doz"
    


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks


    Sonntag, 7. März 2021 15:12

Alle Antworten

  • Hi Guido,
    mit deiner Request-Url greifst du nicht auf die API zu, sondern auf den Seiteninhalt in "collection3". Da du beim Request nichts weiter angegeben hast, wird dir JSon geliefert. Mit ConvertFrom-JSON kannst du dir die Content Eigenschaft holen. Um sinnvolle Ergebnisse zu erreichen, musst du für dein Ziel die API aufrufen (_api).

    Eine Frage bleibt offen: "Warum nutzt du nicht CSOM für den Zugriff?"

    Hier mal ein PowerShell Script für das Auslesen der Titel der Elemente einer Liste:

    <# 
    SharePoint via API auslesen 
    #> 
    
    # ====== Variablen =========
    $Uri = "http://sps2019.lg.loc"
    $USERNAME = 'lg\admin'
    $PASSWORD = 'Passw0rd'
    # ====== ENDE Variablen ====
    
    if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}
    
    # Funktion zum Senden eines WebRequests
    function Execute-GetRequest {
        param(
            [String]$URL
        )
        try{
            # Parameter für Anmeldung
            $nc = [System.Net.NetworkCredential]::new($USERNAME,$PASSWORD)
            # Web-Zugriff
            $wr = [System.Net.WebRequest]::Create($Uri + $URL)
            $wr.Credentials = [System.Net.NetworkCredential]::new($USERNAME,$PASSWORD)
            $wr.Method      = 'GET'
            $wr.Accept      = 'application/xml;odata=verbose'
            $wr.ContentType = "application/xml;odata=verbose"
            # Antwort vorbereiten
            [System.Net.HttpWebResponse]$wresp = $wr.GetResponse()
            # Datenstrom holen
            $responseStream = $wresp.GetResponseStream()
            # Reader zuweisen
            $responseXML = [Xml]([System.IO.StreamReader]($responseStream)).ReadToEnd()
            $responseStream.Close()
            return $responseXML
        }catch {
            if ($_.Exception.InnerException.Response){
                throw ([System.IO.StreamReader]($_.Exception.InnerException.Response.GetResponseStream())).ReadToEnd()
            }else{
                throw $_.Exception.InnerException
            }
        }
    }
    
    $resultXML = Execute-GetRequest("/doz/_api/web/lists/GetByTitle('Liste1')/items")
    
    $Namespace = @{d="http://schemas.microsoft.com/ado/2007/08/dataservices"}
    
    $result = Select-Xml -Xml $res -Namespace $Namespace -XPath "//d:Title"
    
    ForEach ($item in $result) {$item.ToString()}


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks




    Freitag, 5. März 2021 15:35
  • Hi Guido,
    ich habe mir mal die Mühe gemacht, einen PowerShell Script zu schreiben, mit dem aus allen Dokument Bibliotheken incl. der Ordner und Unterordner die Dateien aufgelistet werden (in einer txt Datei), und das mittels REST Api.

    <# 
    SharePoint via API auslesen; alle Dateien anzeigen, incl. Ordner
    #> 
    
    # ====== Variablen =========
    $Uri = "http://sps2019.lg.loc"
    $USERNAME = 'lg\admin'
    $PASSWORD = 'Passw0rd'
    $Namespace = @{ns="http://www.w3.org/2005/Atom"; d="http://schemas.microsoft.com/ado/2007/08/dataservices"; m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"}
    $prot = "c:\temp\log.txt"
    # ====== ENDE Variablen ====
    
    if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}
    
    # Funktion zum Senden eines WebRequests
    function Execute-GetRequest {
        param(
            [String]$URL
        )
        try{
            # Parameter für Anmeldung
            $nc = [System.Net.NetworkCredential]::new($USERNAME,$PASSWORD)
            # Web-Zugriff
            $wr = [System.Net.WebRequest]::Create($Uri + $URL)
            $wr.Credentials = [System.Net.NetworkCredential]::new($USERNAME,$PASSWORD)
            $wr.Method      = 'GET'
            $wr.Accept      = 'application/xml;odata=verbose'
            $wr.ContentType = "application/xml;odata=verbose"
            # Antwort vorbereiten
            [System.Net.HttpWebResponse]$wresp = $wr.GetResponse()
            # Datenstrom holen
            $responseStream = $wresp.GetResponseStream()
            # Reader zuweisen
            $responseXML = [Xml]([System.IO.StreamReader]($responseStream)).ReadToEnd()
            $responseStream.Close()
            return $responseXML
        }catch {
            if ($_.Exception.InnerException.Response){
                throw ([System.IO.StreamReader]($_.Exception.InnerException.Response.GetResponseStream())).ReadToEnd()
            }else{
                throw $_.Exception.InnerException
            }
        }
    }
    
    function Portal([String]$reativeUrl = "/")
    {
        # root Portal
        $resultXML = Execute-GetRequest($reativeUrl + "/_api/web")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) {
            "Portal: ServerRelativeUrl: " + $item.Node.ServerRelativeUrl + " - Title: " + $item.Node.Title | Out-File $prot -append
            ListsBibs($item.Node.ServerRelativeUrl)
        }
        # Subportale
        $resultXML = Execute-GetRequest($reativeUrl + "/_api/site/rootWeb/webinfos")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) {
            "SubPortal: ServerRelativeUrl: " + $item.Node.ServerRelativeUrl + " - Title: " + $item.Node.Title | Out-File $prot -append
            ListsBibs($item.Node.ServerRelativeUrl)
        }
    }
    
    function ListsBibs([String]$relativeUrl)
    {
        $resultXML = Execute-GetRequest($relativeUrl + "/_api/web/lists")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) {
            if ($item.Node.BaseTemplate.'#text' -eq "101") {
                $listenName = $item.Node.Title
                $decodedBibName = [System.Xml.XmlConvert]::DecodeName($item.Node.EntityTypeName)
                "  Liste/Bibliothek " + $item.Node.BaseTemplate.'#text' + " : " + $listenName + " - " + $relativeUrl + "/" + $decodedBibName | Out-File $prot -append
                BibFolders $relativeUrl $decodedBibName 
            }
        }
    }
    
    function BibFolders
    {
        param([String]$relativeUrl, [String]$bibName)
        BibFiles $relativeUrl $bibName
        # Subfolders
        $resultXML2 = Execute-GetRequest($relativeUrl + "/_api/web/GetFolderByServerRelativeUrl('$bibName')/folders") 
        $result2 = Select-Xml -Xml $resultXML2 -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item2 in $result2) {
            "    Subfolder: " + $item2.Node.Name + " - " + $item2.Node.ServerRelativeUrl  | Out-File $prot -append
            $newBibName2 = $bibName + "/" + $item2.Node.Name
            BibFolders $relativeUrl $newBibName2
            }
    #    }
    }
    
    function BibFiles
    {
        param([String]$relativeUrl, [String]$bibName)
        $resultXML = Execute-GetRequest($relativeUrl + "/_api/web/GetFolderByServerRelativeUrl('$bibName')/Files")
        $result = Select-Xml -Xml $resultXML -Namespace $Namespace -XPath "//m:properties"
        ForEach ($item in $result) { "      Dokument: " + $item.Node.Name | Out-File $prot -append }
    }
    
    Portal "/doz"
    


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks


    Sonntag, 7. März 2021 15:12
  • Wow, vielen Dank für die Mühe. Ich werde das definitiv schnellstmöglich testen.

    "Warum nutzt du nicht CSOM für den Zugriff?"

    Den Begriff höre ich leider gerade zum ersten mal. Beim Googeln und Bingen taucht im Zusammenhang mit dem Zugriff zum Sharepoint immer in erster Linie die REST-Api auf. In Verbindung damit bin ich jetzt sogar auf eine sehr hilfreiche Seite gestoßen, die ich mal für die Nachwelt hier hinterlassen möchte:

    https://docs.microsoft.com/en-us/sharepoint/dev/general-development/choose-the-right-api-set-in-sharepoint

    CSOM taucht aber auch hier nicht auf (?)... Ich werde mich zu CSOM allerdings noch einmal belesen.

    Eine Frage anbei: Ist es möglich, in PowerShell die aktuellen Nutzer-Credentials auszulesen, ohne diese noch einmal eingeben zu müssen?

    Mein Ziel (Pseudocode):

    $AD = New-Object System.DirectoryServices.DirectorySearcher
    $AD.Filter = "cn=$env:USERNAME"
    
    $UserMail= $AD.FindOne().Properties.mail
    
    #Ab hier PSEUDO-CODE!
    #Windows-Credentials auslesen
    $WindowsCredentials = $PSEUDOcmdlet-Get-WinCredential
    $WebCredentials = $WindowsCredentials
    
    #Windows-Nutzername durch E-Mail ersetzen, Passwort bleibt bestehen
    $WebCredentials.UserName = $UserMail

    Montag, 8. März 2021 08:03
  • Hi Guido,
    die Credentials von Nutzern auszulesen, funktioniert glücklicherweise nicht. Wenn das funktionieren würde, dann wären alle Anstrengungen zur Gewährleistung der Sicherheit sinnlos, da es damit keine Sicherheit gäbe.

    Der SharePoint ist optimal konzipiert für ein Hol-Prinzip, d.h., jeder Anwender entscheidet, was er sehen will, bzw. worüber er informiert werden will. Bei dieser Arbeitsweise ist es nicht notwendig, dass Nutzernamen, Kennworte usw. von anderen Anwendern bekannt sein müssen.

    Deshalb bleibt die Frage: "Wozu benötigst du die Sicherheitsinformation anderer Nutzer?".

    Wo informierst du dich über die API's in SharePoint?

    Index für SharePoint .NET Server, CSOM, JSOM und REST-API | Microsoft Docs


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Montag, 8. März 2021 09:04
  • Die Nutzerinformationen benötige ich ja nur von dem am aktuellen PC angemeldeten Nutzer. Bspw. Max Muster mit der Kennung "mmuster" geht an seinen PC und führt ein Script aus, mit welchem er ein Verzeichnis im SharePoint durchsuchen möchte (wofür er allerdings die E-Mail "m.muster@firma.com" benötigt). Damit er jedoch nicht bei jeder Script-Ausführung seine Daten eingeben muss, müsste das Script diese entweder bei der erstmaligen Eingabe in einer Datei ablegen (sehr unsicher) oder im Idealfall eben aus der aktuellen User-Session auslesen. Es gibt eine Reihe von proprietären Programmen, die bspw. mittels SSO o.ä. in der Lage sind, die aktuellen Nutzerdaten zu verwenden um eine Serveranmeldung durchzuführen. Dort klappt es doch auch irgendwie?

    Zum Thema CSOM teste ich gerade ein wenig herum. Da es sich ja nicht mehr um REST handelt, sollte ich dafür einen neuen Thread aufmachen oder sollte man diesen hier umbenennen?

    Montag, 8. März 2021 09:53
  • Hi Guido,
    wenn basic forms authentication mit Nutzername und Kennwort eingestellt ist und single sign on genutzt wird, dann reicht dafür CredentialCache.DefaultCredentials.

    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Montag, 8. März 2021 10:18
  • Ich habe den Thread umbenannt.

    Leider kommt bei dem REST-Beispiel immer ein TimeOut. Ich habe den Code abgewandelt zu

    $PASSWORD = ConvertTo-SecureString 'MeinPasswort' -AsPlainText -Force

    aber leider ohne Erfolg. Vermutlich mache ich an anderer Stelle etwas falsch?

    $Uri = "https://mycompany.sharepoint.com" (...) Portal "https://mycompany.sharepoint.com/sites/collection3/sp1/Production" #->TimeOut

    Portal "/sites/collection3/sp1/Production" #->TimeOut


    Zu CSOM: Dazu finden sich glücklicherweise einige Samples im Internet für PowerShell, allerdings tritt bei mir ein Fehler auf, den ich mit den Tutorials nicht nachvollziehen kann.

    Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll" Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll" $URL = "https://mycompany.sharepoint.com/sites/collection3/sp1/Production" $CTX = New-Object Microsoft.SharePoint.Client.ClientContext($URL) if ($Credential -eq $null) {$Credential = get-credential "MeineEmail@firma.com"} $CTX.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Credential.UserName, $Credential.Password) $CTX.Load($CTX.Web) $CTX.ExecuteQuery()
    $CTX.Web
    format-default : The collection has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.
        + CategoryInfo          : NotSpecified: (:) [format-default], CollectionNotInitializedException
        + FullyQualifiedErrorId : Microsoft.SharePoint.Client.CollectionNotInitializedException,Microsoft.PowerShell.Commands.FormatDefaultCommand

    Der Namespace Microsoft.SharePoint erscheint bei mir auch gar nicht... Das SharePoint Online SDK habe ich allerdings installiert (Quelle https://www.microsoft.com/en-us/download/details.aspx?id=42038).
    Montag, 8. März 2021 10:57
  • Hi Guido,
    wenn ein Time Out kommt, dann kann mit der Url nicht zugegriffen werden. Ursache dafür können eine falsche Url, Blockierungen durch firewall oder auch fehlende Rechte im SharePoint sein. Das solltest du untersuchen. Vielleicht ist irgendwo ein kleiner Vertipper. Funktioniert denn die Url aus dem Browser mit den gleichen Kontoangaben? Hast du es mal mit ausgeschalteten firewalls getestet?

    Bevor du im Script etwas änderst, solltest du den Script unverändert starten (lediglich Url, Nutzername, Kennwort fest eintragen). Wenn das funktioniert, dann solltest du schrittweise umbauen (z.B. dein ConvertTo-SecureString).

    Ich empfehle dir, sich für eine Technologie zu entscheiden und erst einmal dabei zu bleiben.


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Montag, 8. März 2021 11:38
  • Ich habe festgestellt, dass ich ein "/" vergessen habe beim aufrufen von Portal().

    Der Timeout ist also weg. Allerdings kommt eine andere Fehlermeldung:

    $Uri = "https://mycompany.sharepoint.com"
    $USERNAME = "meineemail@firma.com"
    $PASSWORD = "Passwort"
    
    #(...)
    
    Portal "/sites/collection3/sp1/Production"
    In Zeile:39 Zeichen:13
    +             throw ([System.IO.StreamReader]($_.Exception.InnerExcepti ...
    +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : OperationStopped: (:String) [], RuntimeException
        + FullyQualifiedErrorId :

    Zeile 39 = throw ([System.IO.StreamReader]($_.Exception.InnerException.Response.GetResponseStream())).ReadToEnd()

    Wenn ich mit dem Debugger Schritt für Schritt durchgehe bricht er ab bei...

    Angehalten bei: [System.Net.HttpWebResponse] $wresp = $wr.GetResponse()
    
    Angehalten bei: if ($_.Exception.InnerException.Response){

    An der Firewall kann ich leider keine Änderungen vornehmen. Diese wird von der Konzern-IT verwaltet. Wenn du mir jedoch konkrete Ports benennen kannst, kann ich deren Freigabe beantragen.

    Zum Thema Technologie: Mein Endziel lautet, Dokumente aus dem SharePoint heraus direkt aufrufen zu können, ohne dass sich der Nutzer mittels Webbrowser durch die ganzen Bibliotheken kämpfen muss. Ob dies letzten Endes mittels REST oder CSOM geschieht, ist mir prinzipiell egal. Da ich allerdings nicht tagtäglich in den Quellcodes herumwühlen werde, sollte es natürlich etwas "anfängertaugliches" sein was nach Möglichkeit viele Schritte automatisch ausführt und ohne diverse Umwege über zusätzliche Parser und Converter (falls dies geht).

    Hier bin ich also ganz auf deine Expertise angewiesen, da du davon ja einiges zu verstehen scheinst. ;-)

    • Bearbeitet Guido_T Montag, 8. März 2021 12:10
    Montag, 8. März 2021 12:02
  • Hi Guido,
    bau in den Catch scope einfach nur mal eine einfache Auswertung ein und zeige, was da in log.txt angezeigt wird:

        }catch {
            $_.Exception.Message | Out-File $prot -append
            if ($_.Exception.InnerException.Response){
                throw ([System.IO.StreamReader]($_.Exception.InnerException.Response.GetResponseStream())).ReadToEnd()
            }else{
                throw $_.Exception.InnerException
            }
        }

    Ich wette, dass die Url für die WebSite falsch ist:

    https://mycompany.sharepoint.com/sites/collection3/sp1/Production

    Wenn du diese Url im Browser eingibst, wird da das gewünschte Portal (WebSite) angezeigt?


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks




    Montag, 8. März 2021 12:21
  • log.txt:

    Ausnahme beim Aufrufen von "GetResponse" mit 0 Argument(en):  "Der Remoteserver hat einen Fehler zurückgegeben: (404) Nicht gefunden."

    Ja, die Original-URL funktioniert im Browser.

    Beim Debuggen ist mir jedoch auch aufgefallen, dass die Zeile

    $wr = [System.Net.WebRequest]::Create($Uri + $URL)
    auf folgende URL hinausläuft: https://mycompany.sharepoint.com/sites/collection3/sp1/Production/_api/web

    Kann man denn /_api/web von jedem Sharepoint-Verzeichnis aus aufrufen?

    Ich habe die letzte Zeile im Script abgewandelt zu:

    Portal "/sites/collection3/sp1"
    Nun lautet die Fehlermeldung:

    <?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code>-2147024891, System.UnauthorizedAccessException</m:code><m:message xml:lang="en-US">Access
    denied. You do not have permission to perform this action or access this resource.</m:message></m:error>
    In Zeile:43 Zeichen:13
    +             throw ([System.IO.StreamReader]($_.Exception.InnerExcepti ...
    +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : OperationStopped: (<?xml version="...sage></m:error>:String) [], RuntimeException
        + FullyQualifiedErrorId : <?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code>-2147024891, System.UnauthorizedAccessException</m:code><m:messa
       ge xml:lang="en-US">Access denied. You do not have permission to perform this action or access this resource.</m:message></m:error>

    log.txt:

    Ausnahme beim Aufrufen von "GetResponse" mit 0 Argument(en):  "Der Remoteserver hat einen Fehler zurückgegeben: (403) Unzulässig."

    Im besagten SharePoint habe ich allerdings Owner-Rechte.

    Montag, 8. März 2021 13:14
  • Hi Guido,
    wenn der Server mit 404 antwortet, dann ist die URL falsch. In deinem Fall hat er unter der Websitesammling "https://mycompany.sharepoint.com/sites/collection3" das Subweb "sp1" und darunter das Subweb "Production" nicht gefunden. Das Script soll dann in diesem Subweb "Produktion" die Bibliotheken auflisten. Dazu muss aber das Subweb auch vorhanden sein. Und das scheint mir bei dir nicht der Fall zu sein.

    Im dem Subweb "Production" übergeordneten Subweb "sp1" hast du keinen Zugriff mit dem genutzten Konto.

    Du solltest erst einmal genau überlegen, was du willst, dann ggf. erst einmal mit einem ausreichend berechtigten Konto testen.


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Montag, 8. März 2021 15:32
  • Hi Guido,
    hier mal ein PowerShell Script mit CSOM. Die beiden erforderlichen dll habe ich in den Unterordner Cloud kopiert.

    <# 
    SharePoint via CSOM auslesen; alle Dateien anzeigen, incl. Ordner
    #> 
    
    Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Cloud\Microsoft.SharePoint.Client.dll"
    Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Cloud\Microsoft.SharePoint.Client.Runtime.dll"
    
    if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}
    
    # ====== Variablen =========
    $SiteUrl = "https://xxx.sharepoint.com"
    $Username = 'xxx@xxx.onmicrosoft.com'
    $Password = 'xxx'
    $prot = "c:\temp\log.txt"
    # ====== ENDE Variablen ====
    
    #Create Credential object from given user name and password
    $Cred = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, (ConvertTo-SecureString $Password -AsPlainText -Force))
        
    #Set up the context
    $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
    $Ctx.Credentials = $Cred
    
    function Portal()
    {
        try{
        # Get the Web Object
        $Web = $Ctx.web
        $Ctx.Load($Web)
        $Ctx.ExecuteQuery()
        "Portal: ServerRelativeUrl: " + $Web.ServerRelativeUrl + " - Title: " + $Web.Title | Out-File $prot -append
        # Lists / Bibs für Hauptportal
        ListsBibs($Web)
        # Subportale
        SubPortals($Web)
            }catch {
            $ex = $_.Exception
            while($ex -ne $null) {
                $ex.Message | Out-File $prot -append
                $ex = $ex.InnerException
            }
            throw $_
        }
    }
    
    function SubPortals([Microsoft.SharePoint.Client.Web]$web)
    {
        $Webs =$Web.Webs
        $ctx.Load($Webs)
        $Ctx.ExecuteQuery()
        ForEach ($item in $Webs) {
            "SubPortal: ServerRelativeUrl: " + $item.ServerRelativeUrl + " - Title: " + $item.Title | Out-File $prot -append
            ListsBibs($item)
            SubPortals($item)
        }
    }
    
    function ListsBibs([Microsoft.SharePoint.Client.Web]$web)
    {
        $Lists = $Web.Lists
        $ctx.Load($Lists)
        $Ctx.ExecuteQuery()
        # Listen / Bibliotheken
        ForEach ($item in $Lists) {
            if ($item.BaseTemplate -eq "101") {
                $listenName = $item.Title
                "  Liste/Bibliothek " + $item.BaseTemplate + " : " + $listenName + " - " + $Web.ServerRelativeUrl + "/" + $listenName | Out-File $prot -append
                BibFolders $item
            }
        }
    }
    
    function BibFolders([Microsoft.SharePoint.Client.List]$ListBib)
    {
        BibFiles $ListBib.RootFolder
        # Subfolders
        $Folders = $ListBib.RootFolder.Folders
        $ctx.Load($Folders)
        $Ctx.ExecuteQuery()
        ForEach ($item in $Folders) {
            "    Subfolder: " + $item.Name + " - " + $item.ServerRelativeUrl  | Out-File $prot -append
            BibFiles $item
            }
    #    }
    }
    
    function BibFiles([Microsoft.SharePoint.Client.Folder]$Folder)
    {
        $Files = $Folder.Files
        $ctx.Load($Files)
        $Ctx.ExecuteQuery()
        ForEach ($item in $Files) { "      Dokument: " + $item.Name | Out-File $prot -append }
    }
    
    "-------- Analyse" | Out-File $prot
    
    Portal


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Dienstag, 9. März 2021 07:54