Loading...

Follow The SharePoint Code on Feedspot

Continue with Google
Continue with Facebook
or

Valid

PowerApps and SharePoint are very well integrated and you can quickly build some great PowerApp forms binded to SharePoint list in just a couple of minute.

Unfortunatly there are some restrictions like for example you cannot bind a survey list type to PowerApps.

But hey it is your lucky day I will guide you through how you can build a PowerApps application that dynamically retrieves all the surveys in a SharePoint site and lets you respond to a survey.

The PowerApps application will have two screens, one to display all the available surveys and one to fill the survey.

In order to retrieve the available survey we will have a Flow named GetAvailableSurveys that is triggered from PowerApps that queries the SharePoint list API. For a fact a survey type list has a BaseTemplate equals to 102 so the REST API call is just

URL_OF_YOUR_SHAREPOINT_SITE/_api/web/lists?$filter=BaseTemplate eq 102

 

This is the Flow corresponding block

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

I am sharing a small PowerShell script to create at glance Office 365 users in Azure AD and assign them the E3 developer license.

$license = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$licenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses

$license.SkuId = (Get-AzureADSubscribedSku | Where-Object -Property SkuPartNumber -Value "DEVELOPERPACK" -EQ).SkuID

$licenses.AddLicenses = $license

$PasswordProfile=New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
$PasswordProfile.Password="YOUR_DEFAULT_PASSWORD"

Import-Csv .\users.csv | foreach-object {
	$usernameSplit = $_.UserName.Split("@")
	$usernameSplit[0]
	$usernameSplit[1]
	$userDisplayNameSplit = $usernameSplit[0].Split(".")
	$userDisplayNameSplit[0]
	$userDisplayNameSplit[1]
	$mailNickName = ($usernameSplit[0])
	$userprinicpalname = $usernameSplit[0] + "@YOUR_DEV_TENANT_NAME.onmicrosoft.com"
	$displayNanme = ($usernameSplit[0] + " " + $usernameSplit[1])
	
	New-AzureADUser -DisplayName $displayNanme -GivenName $userDisplayNameSplit[0] -SurName $userDisplayNameSplit[1] -UserPrincipalName $userprinicpalname -PasswordProfile $PasswordProfile -mailNickName $mailNickName -UsageLocation CH -AccountEnable $true
	# Call the Set-AzureADUserLicense cmdlet to set the license.
	Set-AzureADUserLicense -ObjectId $userprinicpalname -AssignedLicenses $licenses
}

The cvs file is just one column name UserName containing email in the form of 
FIRSTNAME.LASTNAME@company.com

If you have emails in other format you can just modify the lines before the New-AzureADUser command.

Happy coding !

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Taxonomy in the the Term Store of SharePoint is great. It enables to create a hierarchy of simple and friendly Urls.

Portal like Google site offer out of the box components that will display child term of a certen navigation term as url in the page, like that it will be super easy to create a contextual navigation.

The idea here is to have an equivalent webpart for SharePoint using a SPFx webpart.

So to sum up the need. We want a webpart that will display a list of child terms urls based on the current page or term set item.

First create a classic WebPart based on the React framework with yo.

The webpart .ts code is pretty straight forward, we just need to passe the siteurl from the context to our React element so in the render() function just add those lines

public render(): void {
    const element: React.ReactElement<ITaxonomyNavigationProps > = React.createElement(
      TaxonomyNavigation,
      {
        description: this.properties.description,
         siteUrl: this.context.pageContext.web.absoluteUrl
      }
    );

    ReactDom.render(element, this.domElement);
  }

Now in our React component, what we want to achieve is to get all the child term set items from our current term set item. To do that we will need to interact with the taxonomy service. First we need to load the taxonomy scripts, get a reference to the termstore and to the root termstore group with 

private _loadSPJSOMScripts() {
    const siteColUrl = Utils.getSiteCollectionUrl();
    try {
      SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/init.js', {
        globalExportsName: '$_global_init'
      })
        .then((): Promise<{}> => {
          return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/MicrosoftAjax.js', {
            globalExportsName: 'Sys'
          });
        })
        .then((): Promise<{}> => {
          return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.Runtime.js', {
            globalExportsName: 'SP'
          });
        })
        .then((): Promise<{}> => {
          return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.js', {
            globalExportsName: 'SP'
          });
        })
        .then((): Promise<{}> => {
          return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.taxonomy.js', {
            globalExportsName: 'SP'
          });
        })
        .then((): void => {
          this.setState({ loadingScripts: false });
          const context: SP.ClientContext = new SP.ClientContext(this.props.siteUrl);
          let taxSession =  SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
          let termStore  = taxSession.getDefaultSiteCollectionTermStore();
          let termGroups = termStore.get_groups();
          let termGroup = termGroups.getByName("YOUR_ROOT_TERMGROUP_NAME");
          let termSets = termGroup.get_termSets();
          this.loadTermStore(termSets, context);
        })
        .catch((reason: any) => {

        });
    } catch (error) {

    }
  }

Here Utils.getSiteCollectionUrl() is just a helper function to get the site collection url. Once we have loaded all the requered script and got our reference to our main TermGroup we can load and enum all the termset with

private loadTermStore(termSets: SP.Taxonomy.TermSetCollection,spContext:SP.ClientContext ){

    var reactHandler = this;

    let termSet = termSets.getByName("NAVIGATION");
    let terms = termSet.getAllTerms();

    spContext.load(terms, 'Include(Name, Parent, IsRoot,Id,PathOfTerm)');

    spContext.load(terms);
    spContext.load(termSet);
    let termStore:any[]=[];
    let childTerm:any[]=[];
    spContext.executeQueryAsync(function () {

      var termsEnum = terms.getEnumerator();


      while (termsEnum.moveNext()) {

        var spTerm = termsEnum.get_current();
        termStore.push({label:spTerm.get_name(),value:spTerm.get_name(), id:spTerm.get_id(), pathOfTerm:spTerm.get_pathOfTerm(),pathOfParentTerm:  spTerm.get_isRoot()?"":spTerm.get_parent().get_pathOfTerm()});

      }

      window['termStore']= termStore;

      let currentTermUrl = document.location.href.replace(document.location.search,'').replace(spContext.get_url()+'/','').replace(/\//g,';').replace(/-/g,' ').toLowerCase();
      termStore.filter((e) => e.pathOfParentTerm.toLowerCase() === currentTermUrl).forEach( term =>{
        childTerm.push({Url:reactHandler.props.siteUrl + "/" + term.pathOfTerm.replace(/;/g,'/').replace(/ /g,'-'),Description:term.value});
      });

      reactHandler.setState({
        items: childTerm
      });

    });
  }

Here in termSets.getByName("NAVIGATION") I am supposing that the root termset is called NAVIGATION but you can replace it with another name to fit your environment.

The ligne "let currentTermUrl = document.location.href.replace(document.location.search,'').replace(spContext.get_url()+'/','').replace(/\//g,';').replace(/-/g,' ').toLowerCase();" can surely be optimzed the idea is to get the name of the current term name like for example if you are viewing the page https://yourtenant.sharepoint.com/main/news then "main/news" is our current termname url.

After that just a filter to get only the child for this termname.

One more thing window['termStore']= termStore; can be reused as well to save time by not loading the hole termstore but I will leave it up to you guys :)

The complete script is available at https://github.com/alaabitar/spfx


 

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

For those of you who are familliar with PnP Provisioning you might have noticed that out of the box when you save a template and you want to apply your template to another site the document library contents is not included.

I will show you how to add some subroutines to include document library content in your template.xml file.

First we will connect to the target site and initialize some variable

Connect-PnPOnline -Url $siteURL -Credentials $credential; 
Write-Output "Connected!"

$web = Get-PnPWeb

$siteTitle = $web.Title

$saveDir = "C:\Template\" + $siteTitle + "\"

Then just trigger the Get-PnPProvisioningTemplate function 

Get-PnPProvisioningTemplate -Out $($saveDir + "Template.xml") -Force -PersistBrandingFiles -PersistPublishingFiles -IncludeNativePublishingFiles -Handlers Navigation, Lists,PageContents, Pages, Files

I am specifying only fice handlers but you can go with the defaults ones

This will create a Template.xml file but no references to files in any document library.

Now the fun begins

Using the Add-PnPFileToProvisioningTemplate command we will add entry in the Template.xml file to reference a file we want to include in our template. The idea her is to create a subroutine that will parse all the document libray in the site and read all the files and include a reference to those files in the Template.xml

This is how it can be done

First two subroutine to loop through all the folder and subfolder of a document libray

function ProcessFolder($folderUrl, $destinationFolder) {
	Write-Output "Folder URL " $folderUrl  " destinationFolder " $destinationFolder
    $folder = Get-PnPFolder -RelativeUrl $folderUrl
    $tempfiles = Get-PnPProperty -ClientObject $folder -Property Files
   
    if (!(Test-Path -path $destinationfolder )) {
        $dest = New-Item $destinationfolder -type directory 
    }

    $total = $folder.Files.Count
    For ($i = 0; $i -lt $total; $i++) {
        $file = $folder.Files[$i]
        
        Get-PnPFile -ServerRelativeUrl $file.ServerRelativeUrl -Path $destinationfolder -FileName $file.Name -AsFile -Force	

		Add-PnPFileToProvisioningTemplate -Path ($saveDir + "Template.xml") -Source ($destinationfolder + "\" + $file.Name) -Folder $folderUrl -FileLevel Published
		
    }
	
}

function ProcessSubFolders($folders, $currentPath) {
    foreach ($folder in $folders) {
        $tempurls = Get-PnPProperty -ClientObject $folder -Property ServerRelativeUrl    
        #Avoid Forms folders
        if ($folder.Name -ne "Forms") {
            $targetFolder = $currentPath +"\"+ $folder.Name;
            ProcessFolder $folder.ServerRelativeUrl.Substring($web.ServerRelativeUrl.Length) $targetFolder 
            $tempfolders = Get-PnPProperty -ClientObject $folder -Property Folders
            ProcessSubFolders $tempfolders $targetFolder
        }
    }
}

Note here the

Get-PnPFile -ServerRelativeUrl $file.ServerRelativeUrl -Path $destinationfolder -FileName $file.Name -AsFile -Force

This will download the file and save it in the destination folder locally. Then with 

Add-PnPFileToProvisioningTemplate -Path ($saveDir + "Template.xml") -Source ($destinationfolder + "\" + $file.Name) -Folder $folderUrl -FileLevel Published

it will add an entry to the Template.xml file referencing the file we just downloaded.

What is left now is to loop through all  the document libraries and call our subroutines

$docLibs = Get-PNPList | Where-Object{$_.BaseTemplate -eq 101}

    Write-Output "getting doc list"

    foreach( $doc in $docLibs ){

            if( $doc.Title -ne "Site Assets"){
                #Download root files
                ProcessFolder $doc.Title ($saveDir + $doc.Title)
                
                #Download files in folders
                $tempfolders = Get-PnPProperty -ClientObject $doc.RootFolder -Property Folders
                ProcessSubFolders $tempfolders $($saveDir + $doc.Title) + "\"
            }
    }

Here the $_.BaseTemplate -eq 101 is to get only document libraries and the $doc.Title -ne "Site Assets" is just to skip the Site Assets library.

Now you have a beautifull Template.xml file with all your document library files references. You just need to apply it to another site with

Connect-PnPOnline -url $destination -Credentials $credential;

Apply-PnPProvisioningTemplate -path ($saveDir + "Template.xml") -Handlers Navigation, Lists, Pages, Files -ClearNavigation

The complete script is available at https://github.com/alaabitar/provisioning/blob/master/script.ps1

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

To be able to inject CSS in the SharePoint Online Portal you have to use the Application Customizer template when you create an SPFx webpart.
Then it is just a matter of create a style element and injecting it in the head of the page.
So launch your PowerShell window and type

yo @microsoft/sharepoint

Choose a name for your solution, then choose SharePoint Online only as target, choose a folder, don't allow tenant admin to be able to deploy the solution on all sites.
The client-side component in our case is an extension and the type of extension is Application Customizer. Give it a name and a description and wait for the project creation.
When it is finished type

code .

to open visual code.

This is how your shell should look like

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Separate tags by commas
To access this feature, please upgrade your account.
Start your free month
Free Preview