Is there any PowerShell to detect if a SharePoint Online file or folder is using a shared link?

frob 4,216 Reputation points
2022-09-02T23:09:14.03+00:00

Hi there
Is there any PowerShell to detect if a SharePoint Online file or folder is using a shared link?
(Shared link: A SharePoint Online item shared by clicking the ellipsis next the to the item name, then clicking on Share)
Thanks.

SharePoint
SharePoint
A group of Microsoft Products and technologies used for sharing and managing content, knowledge, and applications.
9,560 questions
Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,353 questions
0 comments No comments
{count} votes

Accepted answer
  1. Jing Sun_MSFT 941 Reputation points
    2022-09-05T06:43:20.283+00:00

    Hi @frob ,
    According to your situation, have a try to use below script by CSOM to see if it solves your issue.

    #Add required references to SharePoint client assembly to use CSOM  
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")  
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")  
      
    C:\filepath\Load-CSOMProperties.ps1  
    $Result = @()  
      
    #Specify SharePoint or OneDrive site admin account  
    $adminAccount = user@<tenant-name>.onmicrosoft.com  
    $adminPwd = "<user_password>"  
        
    #Specify SharePoint Online Site URL or User's OneDrive Site URL  
    $siteURL = https://<tenant-name>.sharepoint.com/sites/site_name  
    #$siteURL = https://<tenant-name>-my.sharepoint.com/personal/username_domainame_com  
    $documentLibrary ="Documents"  
      
    #Connect and Load SharePoint Library and Root Folder  
    $secPwd = $(ConvertTo-SecureString $adminPwd -asplaintext -force)  
    $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)  
    $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($adminAccount,$secPwd)  
    $ctx.credentials = $credentials  
    $list = $ctx.Web.Lists.GetByTitle($documentLibrary)  
    $ctx.Load($list)  
    $ctx.ExecuteQuery()  
    $camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery  
    $camlQuery.ViewXml ="<View Scope='RecursiveAll' />";  
    $allItems=$list.GetItems($camlQuery)  
    $ctx.Load($allItems)  
    $ctx.ExecuteQuery()  
      
    $i = 0;  
    $TotoalFiles = $allItems.Count  
      
    foreach($item in $allItems)  
    {  
      
    $i++  
    Write-Progress -activity "Processing $($item["FileRef"])" -status "$i out of $TotoalFiles completed"  
      
    Load-CSOMProperties -object $item -propertyNames @("HasUniqueRoleAssignments");  
    $ctx.ExecuteQuery()  
      
    if($item.HasUniqueRoleAssignments) {  
      
    $sharingInfo = [Microsoft.SharePoint.Client.ObjectSharingInformation]::GetObjectSharingInformation($ctx, $item, $false, $false, $false, $true, $true, $true, $true);  
    $ctx.Load($sharingInfo);  
    $ctx.ExecuteQuery();  
      
    ForEach($shareLink in $sharingInfo.SharingLinks) {  
    if($shareLink.Url)  
    {  
      
    $linkAccess="ViewOnly"  
    if($shareLink.IsEditLink){  
    $linkAccess="Edit"  
    } elseif($shareLink.IsReviewLink){  
    $linkAccess="Review"  
    }  
      
    $Result += New-Object PSObject -property $([ordered]@{  
    FileID = $item.FieldValues["UniqueId"]  
    Name  = $item.FieldValues["FileLeafRef"]              
    FileType = $item.FieldValues["File_x0020_Type"]  
    RelativeURL = $item.FieldValues["FileRef"]  
    CreatedByEmail = $item.FieldValues["Author"].Email  
    CreatedOn  = $item.FieldValues["Created"]  
    Modified   = $item.FieldValues["Modified"]  
    ModifiedByEmail  = $item.FieldValues["Editor"].Email  
    ShareLink  = $shareLink.Url  
    ShareLinkAccess  =  $linkAccess  
    ShareLinkType  = $shareLink.LinkKind  
    AllowsAnonymousAccess  = $shareLink.AllowsAnonymousAccess  
    IsActive  = $shareLink.IsActive  
    })  
      
    }  
    }  
    }  
    }  
      
    $Result | Export-CSV "C:\SharedFileDetails.CSV" -NoTypeInformation -Encoding UTF8  
    

    Please remember to put Load-CSOMProperties.ps1 in your filepath C:\filepath\Load-CSOMProperties.ps1
    Load-CSOMProperties.ps1:

    <#  
    .Synopsis  
        Facilitates the loading of specific properties of a Microsoft.SharePoint.Client.ClientObject object or Microsoft.SharePoint.Client.ClientObjectCollection object.  
    .DESCRIPTION  
        Replicates what you would do with a lambda expression in C#.   
        For example, "ctx.Load(list, l => list.Title, l => list.Id)" becomes  
        "Load-CSOMProperties -object $list -propertyNames @('Title', 'Id')".  
    .EXAMPLE  
        Load-CSOMProperties -parentObject $web -collectionObject $web.Fields -propertyNames @("InternalName", "Id") -parentPropertyName "Fields" -executeQuery  
        $web.Fields | select InternalName, Id  
    .EXAMPLE  
       Load-CSOMProperties -object $web -propertyNames @("Title", "Url", "AllProperties") -executeQuery  
       $web | select Title, Url, AllProperties  
    #>  
    function global:Load-CSOMProperties {  
        [CmdletBinding(DefaultParameterSetName='ClientObject')]  
        param (  
            # The Microsoft.SharePoint.Client.ClientObject to populate.  
            [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObject")]  
            [Microsoft.SharePoint.Client.ClientObject]  
            $object,  
       
            # The Microsoft.SharePoint.Client.ClientObject that contains the collection object.  
            [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObjectCollection")]  
            [Microsoft.SharePoint.Client.ClientObject]  
            $parentObject,  
       
            # The Microsoft.SharePoint.Client.ClientObjectCollection to populate.  
            [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1, ParameterSetName = "ClientObjectCollection")]  
            [Microsoft.SharePoint.Client.ClientObjectCollection]  
            $collectionObject,  
       
            # The object properties to populate  
            [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ClientObject")]  
            [Parameter(Mandatory = $true, Position = 2, ParameterSetName = "ClientObjectCollection")]  
            [string[]]  
            $propertyNames,  
       
            # The parent object's property name corresponding to the collection object to retrieve (this is required to build the correct lamda expression).  
            [Parameter(Mandatory = $true, Position = 3, ParameterSetName = "ClientObjectCollection")]  
            [string]  
            $parentPropertyName,  
       
            # If specified, execute the ClientContext.ExecuteQuery() method.  
            [Parameter(Mandatory = $false, Position = 4)]  
            [switch]  
            $executeQuery  
        )  
       
        begin { }  
        process {  
            if ($PsCmdlet.ParameterSetName -eq "ClientObject") {  
                $type = $object.GetType()  
            } else {  
                $type = $collectionObject.GetType()   
                if ($collectionObject -is [Microsoft.SharePoint.Client.ClientObjectCollection]) {  
                    $type = $collectionObject.GetType().BaseType.GenericTypeArguments[0]  
                }  
            }  
       
            $exprType = [System.Linq.Expressions.Expression]  
            $parameterExprType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()  
            $lambdaMethod = $exprType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2 -and $_.GetParameters()[1].ParameterType -eq $parameterExprType }  
            $lambdaMethodGeneric = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($type.FullName),System.Object]])"  
            $expressions = @()  
       
            foreach ($propertyName in $propertyNames) {  
                $param1 = [System.Linq.Expressions.Expression]::Parameter($type, "p")  
                try {  
                    $name1 = [System.Linq.Expressions.Expression]::Property($param1, $propertyName)  
                } catch {  
                    Write-Error "Instance property '$propertyName' is not defined for type $type"  
                    return  
                }  
                $body1 = [System.Linq.Expressions.Expression]::Convert($name1, [System.Object])  
                $expression1 = $lambdaMethodGeneric.Invoke($null, [System.Object[]] @($body1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))  
        
                if ($collectionObject -ne $null) {  
                    $expression1 = [System.Linq.Expressions.Expression]::Quote($expression1)  
                }  
                $expressions += @($expression1)  
            }  
       
       
            if ($PsCmdlet.ParameterSetName -eq "ClientObject") {  
                $object.Context.Load($object, $expressions)  
                if ($executeQuery) { $object.Context.ExecuteQuery() }  
            } else {  
                $newArrayInitParam1 = Invoke-Expression "[System.Linq.Expressions.Expression``1[System.Func  
       `2[$($type.FullName),System.Object]]]"  
                       $newArrayInit = [System.Linq.Expressions.Expression]::NewArrayInit($newArrayInitParam1, $expressions)  
              
                       $collectionParam = [System.Linq.Expressions.Expression]::Parameter($parentObject.GetType(), "cp")  
                       $collectionProperty = [System.Linq.Expressions.Expression]::Property($collectionParam, $parentPropertyName)  
              
                       $expressionArray = @($collectionProperty, $newArrayInit)  
                       $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")  
                       $includeMethodGeneric = Invoke-Expression "`$includeMethod.MakeGenericMethod([$($type.FullName)])"  
              
                       $lambdaMethodGeneric2 = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($parentObject.GetType().FullName),System.Object]])"  
                       $callMethod = [System.Linq.Expressions.Expression]::Call($null, $includeMethodGeneric, $expressionArray)  
                          
                       $expression2 = $lambdaMethodGeneric2.Invoke($null, @($callMethod, [System.Linq.Expressions.ParameterExpression[]] @($collectionParam)))  
              
                       $parentObject.Context.Load($parentObject, $expression2)  
                       if ($executeQuery) { $parentObject.Context.ExecuteQuery() }  
                   }  
               }  
               end { }  
           }  
         
       ![237734-microsoftteams-image-8.png][1]  
       **********************************************************************************************  
       If the answer is helpful, please click "**Accept Answer**" and kindly upvote it. If you have extra questions about this answer, please click "**Comment**".  
       Note: Please follow the steps in [our documentation][2] to enable e-mail notifications if you want to receive the related email notification for this thread.  
         
         
         [1]: /api/attachments/237734-microsoftteams-image-8.png?platform=QnA  
           
       [2]: http://**********************************************************************************************+If+the+answer+is+helpful,+please+click+"Accept+Answer"+and+kindly+upvote+it.+If+you+have+extra+questions+about+this+answer,+please+click+"Comment".+Note:+Please+follow+the+steps+in+our+documentation+to+enable+e-mail+notifications+if+you+want+to+receive+the+related+email+notification+for+this+thread.
    
    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Dillon Silzer 54,286 Reputation points
    2022-09-03T03:06:03.773+00:00

    Hi @frob

    I am fairly confident that if :w/s/ in the URL you are hitting a shared link.

    Example

    https://tenant.sharepoint.com/**:w:/s/**site/secretcode

    PowerShell Script

    $url = "https://tenant.sharepoint.com/:w:/s/site/secretcode"  
    if ($url -like "*/:w:/s/*") {   
      write-host "Is a Shared Link"  
    } else {  
      write-host "Is not a Shared Link"  
    }  
    

    Output

    237463-image.png

    --------------------------------------

    If this is helpful please accept answer.


  2. Dillon Silzer 54,286 Reputation points
    2022-09-04T03:11:09.177+00:00

    Hi @frob

    You can try using Microsoft Purview:

    How to identify resources shared with external users

    https://learn.microsoft.com/en-us/microsoft-365/compliance/use-sharing-auditing?view=o365-worldwide#how-to-identify-resources-shared-with-external-users

    ------------------------------------------------------

    If this is helpful please accept answer.