SharePoint Modern Site – Using Multiple Languages and Translate Page Programmatically using CSOM C# and PNP Core Context

Sathish Nadarajan
 
Solution Architect
November 22, 2021
 
Rate this article
 
Views
1362

I was trying to translate the pages created in one Site Collection to other languages and there is a simplest way of doing this using the PNP Core Context.  This is not available in the PNP Framework Context I hope.  (again, this is my opinion as I couldn’t find a way).

Basically, we need to enable the languages and even this can be done through programmatically while provisioning the sites.  But in my case, I have the site already.  Hence, I was about to do this manually through the site settings itself.

Go to the Site Settings and Language settings.  Select the Additional Languages and Click on Save.

 

Let me create a new page.

Created a page

Click on the Translation of the page.

On Click of the button, a new page will get created inside the es folder.

 

 

 

When we wanted to do this for a migrated site, which may have few thousands of pages, doing this is impossible and the migration tools will also not be able to do this.  In that case, writing a small utility to create the translation pages is very useful and effective.

As part of the earlier article, create the context.

Then, the below code will do the translation.

using (var scope = host.Services.CreateScope())

            {

                var pnpContextFactory = scope.ServiceProvider.GetRequiredService<IPnPContextFactory>();

                using (var context = await pnpContextFactory.CreateAsync("DemoSite"))

                {

                    var page = (await context.Web.GetPagesAsync("DemoPage")).FirstOrDefault();

                    await page.TranslatePagesAsync();

                }

            }

The above piece will do the same which we did manually earlier.  Hope this is simple and a good use case.

 

Happy Coding

Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

CSOM – Get the Pages modified after a time stamp by C# CSOM – SharePoint Online

Sathish Nadarajan
 
Solution Architect
October 7, 2021
 
Rate this article
 
Views
1305

In one of the requirements, got to retrieve the files which are modified after a specific time stamp. As part of that, created a CAML Query and thought of sharing with the community with the usecase.

One thing, just wanted to highlight is, the below code uses pnpFramework to get the context. As the earlier PNPCoreOnline is deprecated. Please search for pnpframework on the Nuget Manager.

public static void GetPagesByTimeStamp()
        {
            System.IO.File.AppendAllText(logFilePath, "Started.." + Environment.NewLine + Environment.NewLine);

            string siteUrl = ConfigurationManager.AppSettings["siteUrl"];
            string userName = ConfigurationManager.AppSettings["userName"];
            string password = ConfigurationManager.AppSettings["password"];
            string timestamp = ConfigurationManager.AppSettings["timestamp"];

            try
            {
                using (var ctx = new PnPClientContext(siteUrl))
                {
                    ctx.Credentials = new SharePointOnlineCredentials(userName, password.ToSecureString());
                    //Get the web from the current context
                    Web web = ctx.Web;
                    ctx.Load(web);
                    ctx.Load(web.Lists);
                    ctx.ExecuteQueryRetry();

                    //Get the Pages Library
                    var pages = ctx.Web.Lists.GetByTitle("Site Pages");
                    ctx.Load(pages);

                    //
                    
                    string datetime = Convert.ToDateTime(timestamp).ToString("yyyy-MM-ddTHH:mm:ssZ");
                    //Get all Items inside the Pages Library
                    CamlQuery camlQuery = new CamlQuery();
                    camlQuery.ViewXml = @"<View Scope='Recursive'>
                                             <Query>
                                                <Where>
                                                    <Gt>
                                                        <FieldRef Name='Modified'/>
                                                        <Value IncludeTimeValue='TRUE' Type='DateTime'>" + datetime + "</Value>" + 
                                                    @"</Gt>
                                                </Where>
                                                <OrderBy>
                                                    <FieldRef Name='Modified' Ascending = 'true' />
                                                </OrderBy>
                                            </Query>
                                         </View>";
                    ListItemCollection listItems = pages.GetItems(camlQuery);
                    ctx.Load(listItems);
                    ctx.ExecuteQuery();

                    List<Page> lstPages = new List<Page>();

                    foreach (var listItem in listItems)
                    {
                        System.Console.WriteLine(listItem["Title"]);
                        Page p = new Page();
                        p.Id = Convert.ToInt32(listItem["ID"]);
                        p.Name = Convert.ToString(listItem["Title"]);

                        lstPages.Add(p);


                    }



                    TextWriter txtWriter = new StreamWriter(ConfigurationManager.AppSettings["LogFilePath"] + "Pages.csv");

                    using (CsvWriter writer = new CsvWriter(txtWriter, new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture)))
                    {
                        writer.WriteRecords(lstPages);

                        writer.Flush();

                    }




                }
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("Exception occurred : " + ex.Message);
                System.Console.ReadLine();
            }
        }

The above code is self explanatory and doesn’t require much explanation I guess.


Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

NodeJS – Step by Step Procedure to Setup and Create a NodeJS application and use TypeScript as a Programming Language

Sathish Nadarajan
 
Solution Architect
October 1, 2021
 
Rate this article
 
Views
1016

All of us are familiar about the popularity and the power of Node JS. By default the NodeJS application will have the start page as a JS file and predominantly it is meant for the JavaScript. But, when I wanted to create a Node WebSite, which is going to interact with SharePoint Online using PNPJS, I was in a situation that, TypeScript is more friendlier than JS for any PNP and SharePoint Online coding.

Hence, wanted to explore, how to create or use Typescript within the Node JS application and make the DEV environment a bit faster and cleaner.

In this article, let us see the steps to create a NodeJS application and use typescript as a Programming Language on it.

1. Install Node LTS from the below path.

https://nodejs.org/en/download/

 

 

 

2. Ensure that the Node is installed properly by typing “node -v”. This will give the version of the node installed.

3. Create a folder with the app name – C:\NodeApps\DemoNodeJSWithTypeScript
4. Open the command prompt.

5. Create a solution using “npm init”.

6. The command will create the package.json file.

 

Package.json

{
"name": "demonodejswithtypescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "",
"license": "ISC"
}

7. Install TypeScript using the command npm install -g typescript

8. Ensure the version of typescript by tsc –version

9. We need to create the config file for the TypeScript. Tscofig.json.
a. We can create this by running the command “tsc –init”.

 

10. Now, the tsconfig.json has been created. Have a look on the site, for more information about the tsconfig.
11. Update the tsconfig as below.

{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
"target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": [
"./node_modules"
]
}

12. Create the folders SRC and DIST as updated above on the tsconfig.json file. The folder structure will looks as below.

 

13. Create a file called index.ts inside the src folder.
14. Write some TS code here.

function sum (num1:number, num2:number){
return num1 + num2;
}
console.log(sum(8,4))

15. Now, install the typescript compiler npm install -D typescript

16. Now run tsc ;- this will create the corresponding index.js on the outdir dist folder.

17. Now, the solution and the index.js will be shown as below.

 

18. Now execute “node dist/index.js” to run the application.

 

19. Install ts-node by “npm install -D ts-node
a. Ts-node allows us to point to a Typescript file. It will run .ts, compile it and run it with Node.js for us.
20. Update the config file as below.

{
"name": "demonodejswithtypescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start": "ts-node ./src/index.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
}
}

21. Now we can execute by using “npm start” itself. This will automatically trigger the index.ts file.
22. Then install the below packages for various purposes.

Npm install express
Npm install -D @types/node
Npm install -D @types/express

 

23. Update the index.ts as below.

import express, { Application, ErrorRequestHandler, Request, response, Response } from 'express';

const app: Application = express();
const PORT = process.env.PORT || 2000;

app.get("/", async (req: Request, res: Response): Promise => {

res.send("Hello Typescript with Node.js!")

});

app.use(function (err: any, req: Request, res: Response, next: ErrorCallback) {
res.status(err.status || 500);
res.send(err);
});

app.listen(PORT, (): void => {
console.log(`Server Running here https://localhost:${PORT}`);
});

24. Now run “npm start

 

25. Browse the page http://localhost:2000

 

26. The next step is to continuously monitor the TS files changes, so that the npm server should automatically refresh. For that, I prefer to go with Nodemon.

27. Npm install -D nodemon

28. Update the package.json with the script parameter dev. The final package.json will looks like below.

{
"name": "demonodejswithtypescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start": "ts-node ./src/index.ts",
"dev": "nodemon ./src/index.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.13",
"@types/node": "^16.10.2",
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
},
"dependencies": {
"express": "^4.17.1"
}
}

29. Now we can use the command “npm run dev

Though it’s a bit lengthy, this will be useful for someone who wants to develop a Node application with TypeScript. Especially the stand alone application for the SharePoint Online. Yes, this is a start and the objective is to use this application with PNPJS to interact with SharePoint Online sites in the upcoming articles. Stay Tuned!!!

Download the Source HERE

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

How to Find the User belongs to an Office 365 Group using Graph API in SharePoint Online – SPFx (SharePoint Framework)

Sathish Nadarajan
 
Solution Architect
August 25, 2021
 
Rate this article
 
Views
2060

In this article, let us see whether the logged in user (or any user) belongs to a particular Office 365 Group member/owner or not. The below class is self-explanatory.

GraphDataProvider.ts

//Import the necessary modules.
import { WebPartContext } from "@microsoft/sp-webpart-base";
import validator from 'validator';
import IGraphDataProvider from "./IGraphDataProvider";
import customLogger from '../../../common/services/CustomLogger';

export class GraphDataProvider implements IGraphDataProvider {

    public context: WebPartContext;

    public setup(context: WebPartContext): void {
        this.context = context;
    }

    public isMemberOfOffice365Group = async (groupId: string, userPrinciple: string): Promise<boolean> => {
        try {
            let isMember = false;

            if (validator.isUUID(groupId)) {
                let membersAndOwners = await this.getMembersOfGroup(groupId);
                membersAndOwners = membersAndOwners.map(user => { return user.toLowerCase() })
                isMember = membersAndOwners.indexOf(userPrinciple.toLowerCase()) != -1 ? true : false;
            }
            else {
                isMember = groupId.toLowerCase() == userPrinciple.toLowerCase() ? true : false;
            }
            return isMember;
        } catch (error) {

 
            return false;
        }

    }

    private getMembersOfGroup = async (groupID: string): Promise<string[]> => {
        try {
 

            let membersAndOwners: string[] = [];

            let client = await this.context.msGraphClientFactory.getClient();

            let groupmembers = await client.api(`/groups/${groupID}`).expand('members').get();
            let groupowners = await client.api(`/groups/${groupID}`).expand('owners').get();

            if (groupmembers.members) {
                groupmembers.members.forEach(member => {
                    membersAndOwners.push(member.userPrincipalName);
                })
            }
            if (groupowners.owners) {
                groupowners.owners.forEach(owner => {
                    membersAndOwners.push(owner.userPrincipalName);
                })
            }
            return membersAndOwners;
        }
        catch (error) {
 
            return null;
        }

    }
}

const graphDataProvider = new GraphDataProvider();
export default graphDataProvider;

IGraphDataProvider.ts

export default interface IGraphDataProvider {
    isMemberOfOffice365Group  (groupId: string, userPrinciple: string): Promise<boolean> ;
}

And on the Consumption Component,

import graphDataProvider from '../../services/GraphDataProvider';

let isMember = await graphDataProvider.isMemberOfOffice365Group(Group, currentUserPrinciple)

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

SPFx – React Barcode.

Sathish Nadarajan
 
Solution Architect
August 24, 2021
 
Rate this article
 
Views
704

In one of the webpart, I must create the barcode based on the value generated. For that, after few analyses, found a react package. Hence sharing the information for easy access.

The package name is, React-Barcode.
Install using the below commands from your VS Code Terminal.

npm i react-barcode
https://www.npmjs.com/package/react-barcode

The usage is as below.

import * as React from 'react';
import styles from './Barcodesample.module.scss';
import { IBarcodesampleProps } from './IBarcodesampleProps';
import { escape } from '@microsoft/sp-lodash-subset';
import Barcode from 'react-barcode';

export default class Barcodesample extends React.Component<IBarcodesampleProps, {}> {
  public render(): React.ReactElement<IBarcodesampleProps> {
    return (
      <div className={ styles.barcodesample }>

<div id={`${this.state.MyDynamicValue}`}  >
<div>
         <Barcode value={"000000000321"}
                width={2}
                height={100}
                format={"CODE128"}
                displayValue={true}
                textAlign={"center"}
                background={"#fff"}
                lineColor="#000000"
                margin={0}
                fontSize={20}
              /> 
</div>
</div>
      </div>
    );
  }
}

The Output is as below.

The tag Barcode is rendered as div with svg format image.

To store this, we can use the below lines of code.

let itemNosvg = document.getElementById(`${this.state.MyDynamicValue }`).getElementsByTagName("svg")[0];
        let itemNoblob = new Blob([new XMLSerializer().serializeToString(itemNosvg)], { type: 'image/svg+xml' });
        await sp.web.getFolderByServerRelativeUrl(`${serverRelativeUrl}/ Images/Barcodes`).files.add(`${this.state.MyDynamicValue}.svg`, itemNoblob, true);

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

SPFx – A Simple Logging and Exception Handling Mechanism in SharePoint Framework

Sathish Nadarajan
 
Solution Architect
August 14, 2021
 
Rate this article
 
Views
2908

Sometime back, we saw the Logging using the Azure App Insights here. In this article, let us see how to implement the logging and Exception using the SharePoint List in a simpler way.

Create the below classes.

ICustomLogger.ts

import ICustomLogMessage from './ICustomLogMessage';

interface ICustomLogger {
    Log(logMessage: ICustomLogMessage): void;
    Warn(logMessage: ICustomLogMessage): void;
    Verbose(logMessage: ICustomLogMessage): void;
    Error(logMessage: ICustomLogMessage): void;
}

export default ICustomLogger;

CustomLogger.ts

import ICustomLogger from './ICustomLogger'
import ICustomLogMessage from './ICustomLogMessage'
import { sp } from '@pnp/sp/presets/all';
import * as moment from 'moment';

export class CustomLogger implements ICustomLogger {

    public Log = async (logMessage: ICustomLogMessage) => {
        try {
            this.saveLogs(logMessage, "Log");
        } catch (error) {
            //Can't do anything
            console.error(error.Message);
        }
    }

    public Warn = async (logMessage: ICustomLogMessage) => {
        try {
            this.saveLogs(logMessage, "Warn");
        } catch (error) {
            //Can't do anything
            console.error(error.Message);
        }
    }

    public Verbose = async (logMessage: ICustomLogMessage) => {
        try {
            this.saveLogs(logMessage, "Verbose");
        } catch (error) {
            //Can't do anything
            console.error(error.Message);
        }
    }

    public Error = async (logMessage: ICustomLogMessage) => {
        try {
            console.error(logMessage.Message);
            this.saveLogs(logMessage, "Error");
        } catch (error) {
            //Can't do anything
            console.error(error.Message);
        }
    }

    private saveLogs = async (logMessage: ICustomLogMessage, logType: string) => {
        sp.web.lists.getByTitle('ExceptionLogs').items.add({
            WebPartName: logMessage.WebPartName,
            ComponentName: logMessage.ComponentName,
            MethodName: logMessage.MethodName,
            Message: logMessage.Message,

            LogType: logType,
            Date: moment(new Date()).format("MMDDYYYY")
        });
    }
}

const customLogger = new CustomLogger();
export default customLogger;

ICustomLogMessage.ts

interface ICustomLogMessage {
    WebPartName: string,
    ComponentName: string,
    MethodName: string,
    Message: string
}

export default ICustomLogMessage;

I have two interfaces and one implementation class. Now, on the component or any other webpart.ts file, we are going to call the Logging method.

public myMethod = async (): Promise => {
    try {

        customLogger.Log({
            WebPartName: "MyWebPart",
            ComponentName: "MyComponent",
            MethodName: "MyMethod",
            Message: `Entering into the method and the ID is ${Id} and the Country ${country}`
        });

    }
    catch (error) {
        customLogger.Error({
            WebPartName: "MyWebPart",
            ComponentName: "MyComponent",
            MethodName: "MyMethod",
            Message: "Exception Occurred : " + error.message
        });
    }
}

The Log List will be something like,

 

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

PowerShell : Change the Layout of the SharePoint Page

Sathish Nadarajan
 
Solution Architect
July 9, 2021
 
Rate this article
 
Views
2150

In SharePoint Online, when we create a new modern page, by default, the header of the page is coming as below. Sometimes, we don’t want that to be displayed. To hide the title bar, we can change the layout of the page itself.

 

#Connect to SharePoint Online site
$SiteURL = "https://sppalsmvp.sharepoint.com/sites/ReactRepository/"
Connect-PnPOnline $SiteURL -Credential (Get-Credential)

#Get the ID of the Page
Get-PnPListItem -List SitePages

#Change Page layout from "Article" to "Home"
Set-PnPListItem -List SitePages –Identity "13" -Values @{"PageLayoutType"="Home"}

Then, the title bar will not appear on this layout.

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

CSOM – PowerShell Script – Import Nintex Workflows in SharePoint

Sathish Nadarajan
 
Solution Architect
February 10, 2021
 
Rate this article
 
Views
1006

In this article, let us see how to Import Nintex Workflows in SharePoint using CSOM PowerShell Script.
The code is self-explanatory.

01.ImprotNintexForms

function Import-NintexWorkflow {
<# .SYNOPSIS Imports a Nintex workflow .nwf file to a list. .DESCRIPTION Imports a Nintex workflow .nwf file to a list. #>
[CmdletBinding()]
param(
[string]$WebUrl = $(throw "Required parameter -WebUrl missing"),
[string]$listName = $(throw "Required parameter -ListName missing"),
[string[]]$workflowNames = $(throw "Required parameter -workflowNames missing"),
[string]$WorkflowFolderPath = $(throw "Required parameter -WorkflowFolderPath missing"),
[bool]$OverwriteExistingVersion = $false
)
begin {
if(!(Get-PnPConnection)) {
throw "There is no PnPConnection"
}
Write-Host "---- Importing Nintex workflow to '$($listName)' ----" -ForegroundColor Yellow
}
process {
$List = Get-PnPList -Identity $listName
$ListID = $List.Id.ToString()
$ListName = $List.Title
$webServiceUrl = "$WebUrl/_vti_bin/NintexWorkflow/Workflow.asmx"
$webServiceProxy = New-WebServiceProxy -Uri $webServiceUrl -UseDefaultCredential
$webServiceProxy.URL = $webServiceUrl

for ($i=0;$i -lt $workflowNames.Length; $i++) {
$WorkflowName = $workflowNames[$i] + "_6"
$WorkflowFilePath = $WorkflowFolderPath + "" + $workflowNames[$i] + ".nwf"

$nwfContent = Get-Content "$WorkflowFilePath"
$utf8 = New-Object System.Text.UTF8Encoding
[byte[]] $byteData = $utf8.GetBytes($nwfContent.ToString())
$hasWorkflowPublished = $webServiceProxy.WorkflowExists($WorkflowName,$ListID,"List")
Write-Host "Workflow exists status: '$hasWorkflowPublished'" -ForegroundColor Cyan
if($hasWorkflowPublished -eq "NameUsedInOtherList" -or $hasWorkflowPublished -eq "NameUsedInThisList") {
#May be delete it
Write-Host "Workflow already exists '$hasWorkflowPublished', if status is 'NameUsedInOtherList' no changes can be made. Please delete it." -ForegroundColor Cyan
}
if($hasWorkflowPublished -eq "NameNotUsed" -or ($hasWorkflowPublished -eq "NameUsedInThisList" -and $OverwriteExistingVersion -eq $true)){
$IsPublished = $webServiceProxy.PublishFromNWF($byteData, $ListName, $WorkflowName, $true)
if($IsPublished) {
Write-Host "Nintex Workflow '$WorkflowName' successfully published to list '$ListName'" -ForegroundColor Green
} else {
Write-Host "Nintex Workflow '$WorkflowName' could not be published to list '$ListName'" -ForegroundColor Yellow
}
}
}
}
end { }
}

Run.ps1

#=========================================== Description Start ========================================= #
# Deploy from a List

# Author : Sathish Nadarajan
# Date : 03-Feb-2021
#=========================================== Description End====================================== #

# ============================================ PreRequisites Start ================================= #

#Get-Module -Name *pnp*
#Pre Req - SharePoint PnP PowerShell Version 2.25.1804.1

#=============================================PreRequisites End =============================== #

#============================================= Initial Setup Start =============================== #

cls

$Host.UI.RawUI.WindowTitle = "-- Deploy Assets --"

$StartDate = Get-Date
Write-Host -ForegroundColor White "------------------------------------"
Write-Host -ForegroundColor White "| Deploy Assets |"
Write-Host -ForegroundColor White "| Started on: $StartDate |"
Write-Host -ForegroundColor White "------------------------------------"

#Add-PSSnapin Microsoft.SharePoint.PowerShell

$LogTime = Get-Date -Format yyyy-MM-dd_hh-mm-ss

$scriptBase = split-path $SCRIPT:MyInvocation.MyCommand.Path -parent
Set-Location $scriptBase

# Create Log File Folder3
if(!(TEST-PATH ".Logs-$LogTime")){
NEW-ITEM ".Logs-$LogTime" -type Directory
}

# Assign the Log and Progress Files
$TranscriptFile = ".Logs-$LogTimeDeploy.Transcript.rtf"
try{
stop-transcript|out-null
}
catch [System.InvalidOperationException]{}
start-transcript $TranscriptFile

#============================================= Initial Setup End =============================== #

# ============================================ Setup Input Paths Start ================================= #

#connect to the SharePoint list
$sourceWebUrl = 'http://andytest-sp:555/sites/newsite/'
$sourceListname = "AssetRegisterV2"

$outputFolderPath = ".Logs-$LogTime"

$targetWebUrl = 'http://andytest-sp:555/sites/newsite/'
$targetListname = "AssetRegister_Test"
$workflowNames = @('Send Notification When Child is Promoted as Parent','Send Notification When Asset is deleted')

# ============================================ Setup Input Paths End ================================= #

Import-Module ".4.ImportNintexWorkflows.ps1"

Write-Host "Begin to Execute.." -ForeGroundColor Yellow
"Begin to Execute..." | Out-File -FilePath $TranscriptFile -Append

Connect-PnPOnline -Url $sourceWebUrl -CurrentCredentials -ErrorAction Inquire

Import-NintexWorkflow $targetWebUrl $targetListname $workflowNames $outputFolderPath $true
Disconnect-PnPOnline

Write-Host "Update Completed.. Press Enter to Exit" -ForeGroundColor Green

try{
stop-transcript|out-null
}
catch [System.InvalidOperationException]{}

 

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

CSOM – PowerShell Script – Export Nintex Workflows in SharePoint

Sathish Nadarajan
 
Solution Architect
February 9, 2021
 
Rate this article
 
Views
1780

During the time of deployment, we may in a need to export and import the workflows from our DEV environment to the higher environment. In this article, let us see how to Export Nintex Workflows in SharePoint using CSOM PowerShell Script.

The code is self-explanatory.

01.ExportNintexForms

 

function Export-NintexWorkflow {
<# .SYNOPSIS Export a Nintex workflow .nwf file to a local directory .DESCRIPTION Export a Nintex workflow .nwf file to a local directory #>
[CmdletBinding()]
param(
[string]$WebUrl = $(throw "Required parameter -WebUrl missing"),
[string]$listName = $(throw "Required parameter -ListName missing"),
[string[]]$workflowNames = $(throw "Required parameter -WorkflowNames missing"),
[string]$FilePath = $(throw "Required parameter -FilePath missing")
)
begin {
if(!(Get-PnPConnection)) {
throw "There is no PnPConnection"
}
Write-Host "---- Exporting Nintex workflow for '$($List.Title)' ----" -ForegroundColor Yellow
}
process {
$List = Get-PnPList -Identity $listName
$timeStampString = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
# Get XML New File path
$xmlFilePath = "$FilePath$($List.Title)-$timeStampString.nwf"

$webServiceUrl = "$WebUrl/_vti_bin/NintexWorkflow/Workflow.asmx"
$webServiceProxy = New-WebServiceProxy -Uri $webServiceUrl -UseDefaultCredential
$webServiceProxy.URL = $webServiceUrl
$ctx=Get-PnPContext

$ctx.load($List.WorkflowAssociations)
$ctx.ExecuteQuery()

#Get all workflows that are associated with the current list
foreach($listassociation in $List.WorkflowAssociations) {
if($workflowNames.Contains($listassociation.Name)){
$WorkflowName = $listassociation.Name
$workflowContent = $webServiceProxy.ExportWorkflow($WorkflowName, $List.Title, "list")
$xmlFilePath = "$FilePath$WorkflowName.nwf"
#Save XML File to Disk
$workflowContent | Out-File $xmlFilePath
Write-Host "Nintex workflow '$WorkflowName' exported successfully to '$xmlFilePath'" -ForegroundColor Green
}
}
}
end { }
}

 

Run.ps1

#=========================================== Description Start ========================================= #
# Deploy from a List

# Author : Sathish Nadarajan
# Date : 03-Feb-2021
#=========================================== Description End====================================== #

# ============================================ PreRequisites Start ================================= #

#Get-Module -Name *pnp*
#Pre Req - SharePoint PnP PowerShell Version 2.25.1804.1

#=============================================PreRequisites End =============================== #

#============================================= Initial Setup Start =============================== #

cls

$Host.UI.RawUI.WindowTitle = "-- Deploy Assets --"

$StartDate = Get-Date
Write-Host -ForegroundColor White "------------------------------------"
Write-Host -ForegroundColor White "| Deploy Assets |"
Write-Host -ForegroundColor White "| Started on: $StartDate |"
Write-Host -ForegroundColor White "------------------------------------"

#Add-PSSnapin Microsoft.SharePoint.PowerShell

$LogTime = Get-Date -Format yyyy-MM-dd_hh-mm-ss

$scriptBase = split-path $SCRIPT:MyInvocation.MyCommand.Path -parent
Set-Location $scriptBase

# Create Log File Folder3
if(!(TEST-PATH ".Logs-$LogTime")){
NEW-ITEM ".Logs-$LogTime" -type Directory
}

# Assign the Log and Progress Files
$TranscriptFile = ".Logs-$LogTimeDeploy.Transcript.rtf"
try{
stop-transcript|out-null
}
catch [System.InvalidOperationException]{}
start-transcript $TranscriptFile

#============================================= Initial Setup End =============================== #

# ============================================ Setup Input Paths Start ================================= #

#connect to the SharePoint list
$sourceWebUrl = 'http://SourceSiteURL/sites/newsite/'
$sourceListname = "AssetRegisterV2"

$outputFolderPath = ".Logs-$LogTime"

$targetWebUrl = 'http://TargetSiteURL/sites/newsite/'
$targetListname = "AssetRegister_Test"
$workflowNames = @('Send Notification When Child is Promoted as Parent','Send Notification When Asset is deleted')

# ============================================ Setup Input Paths End ================================= #

Import-Module ".3.ExportNintexWorkflows.ps1"

Write-Host "Begin to Execute.." -ForeGroundColor Yellow
"Begin to Execute..." | Out-File -FilePath $TranscriptFile -Append

Connect-PnPOnline -Url $sourceWebUrl -CurrentCredentials -ErrorAction Inquire

Export-NintexWorkflow $sourceWebUrl $sourceListname $workflowNames $outputFolderPath

Disconnect-PnPOnline

Write-Host "Update Completed.. Press Enter to Exit" -ForeGroundColor Green

try{
stop-transcript|out-null
}
catch [System.InvalidOperationException]{}

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

CSOM – PowerShell Script – Export and Import Nintex Forms in SharePoint

Sathish Nadarajan
 
Solution Architect
February 8, 2021
 
Rate this article
 
Views
1907

In this article, let us see how to Export and Import Nintex Forms in SharePoint using CSOM PowerShell Script.
The code is self-explanatory.

01.ExportNintexForms.ps1

function Export-NintexForm {
<# .SYNOPSIS Exports a Nintex form XML to a local directory. .DESCRIPTION Exports a Nintex form XML to a local directory. #>
[CmdletBinding()]
[OutputType([string])]
param(
[string]$WebUrl = $(throw "Required parameter -WebUrl missing"),
[string]$ListName = $(throw "Required parameter -ListName missing"),
[string]$FilePath = $(throw "Required parameter -FilePath missing")
)
begin {
if(!(Get-PnPConnection)) {
throw "There is no PnPConnection"
}
Write-Host "---- Exporting Nintex form for '$ListName' ----" -ForegroundColor Yellow
}
process {
$xmlFilePath ="";
$list = Get-PnPList -Identity $ListName

$addressUrl = "$WebUrl/_vti_bin/NintexFormsServices/NfRestService.svc/GetFormXml"
$addressUri = New-Object System.Uri($addressUrl)

# Create the web request
[System.Net.HttpWebRequest] $request = [System.Net.WebRequest]::Create($addressUri)

# Add authentication to request
$request.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials

# Set type to POST
$request.Method = "POST";
$request.ContentType = "application/json; charset=utf-8";
$request.Accept = "application/json, text/javascript, */*; q=0.01"
$request.Headers.Add("X-Requested-With", "XMLHttpRequest")

# Create the data we want to send
$id = "{$($list.ID)}"
$contentTypeID = "{0x01000***********}" #Give the content Type ID
$data = "{`"contentTypeId`": `"$contentTypeID`", `"listId`": `"$id`" }"

# Create a byte array of the data we want to send
$utf8 = New-Object System.Text.UTF8Encoding
[byte[]] $byteData = $utf8.GetBytes($data.ToString())

# Set the content length in the request headers
$request.ContentLength = $byteData.Length;

# Write data
try {
$postStream = $request.GetRequestStream()
$postStream.Write($byteData, 0, $byteData.Length);
}
catch [Exception]{
Write-Host -f red $_.Exception.ToString()
}
finally {
if($postStream) { $postStream.Dispose() }
}

# Get response
try {
[System.Net.HttpWebResponse] $response = [System.Net.HttpWebResponse] $request.GetResponse()
# Get the response stream
[System.IO.StreamReader] $reader = New-Object System.IO.StreamReader($response.GetResponseStream())

try {
$strResult = $reader.ReadToEnd()
$xml = [System.Web.HttpUtility]:: HtmlDecode($strResult)

#Once Deserialized XML will get generated with below string tag, insted of going for XML manupulation I have removed the string tag from the xml
$xmlExcludedStringTag = $xml -replace "" , "";
$xmlExcludedStringTag = $xmlExcludedStringTag -replace "" , "";

$timeStampString = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
# Get XML New File path
$xmlFilePath = "$FilePath$($ListName)Form-$timeStampString.xml";

#Save XML File to Disk
#$xmlExcludedStringTag | Out-File $xmlFilePath
$enc = [system.Text.Encoding]::Unicode
$encodedData = $enc.GetBytes($xmlExcludedStringTag)
$encodedData | Set-Content $xmlFilePath -Encoding Byte
Write-Host "Nintex form exported successfully to '$xmlFilePath'" -ForegroundColor Green
return $xmlFilePath
}
catch [Exception] {
Write-Host -f red $_.Exception.ToString()
}
}
catch [Exception] {
Write-Host -f red $_.Exception.ToString()
}
finally {
if($response) { $response.Dispose() }
}
$xmlFilePath
}
end { }
}

02.ImportNintexForms.ps1

function Read-FileBytes($Filename)
{
try {
[system.io.stream] $stream = [system.io.File]::OpenRead($Filename)
try {
[byte[]] $filebytes = New-Object byte[] $stream.length
[void] $stream.Read($filebytes, 0, $stream.Length)

return $filebytes
}
finally {
$stream.Close()
}
}
catch {

return
}
return $true;
}

function Get-FormDigest($webUrl )
{
[System.Reflection.Assembly]::LoadWithPartialName("System.IO") >> $null

#$formDigestRequest = [Microsoft.SharePoint.Utilities.SPUtility]::ConcatUrls($webUrl, "_api/contextinfo")
$formDigestRequest = $webUrl + "/_api/contextinfo"
$formDigestUri = New-Object System.Uri($formDigestRequest)
$credCache = New-Object System.Net.CredentialCache
$credCache.Add($formDigestUri, "NTLM", [System.Net.CredentialCache]::DefaultNetworkCredentials)
$spRequest = [System.Net.HttpWebRequest] [System.Net.HttpWebRequest]::Create($formDigestRequest)
$spRequest.Credentials = $credCache
$spRequest.Method = "POST"
$spRequest.Accept = "application/json;odata=verbose"
$spRequest.ContentLength = 0

[System.Net.HttpWebResponse] $endpointResponse = [System.Net.HttpWebResponse] $spRequest.GetResponse()
[System.IO.Stream]$postStream = $endpointResponse.GetResponseStream()
[System.IO.StreamReader] $postReader = New-Object System.IO.StreamReader($postStream)
$results = $postReader.ReadToEnd()

$postReader.Close()
$postStream.Close()

#Get the FormDigest Value
$startTag = "FormDigestValue"
$endTag = "LibraryVersion"
$startTagIndex = $results.IndexOf($startTag) + 1
$endTagIndex = $results.IndexOf($endTag, $startTagIndex)
[string] $newFormDigest = $null
if (($startTagIndex -ge 0) -and ($endTagIndex -gt $startTagIndex))
{
$newFormDigest = $results.Substring($startTagIndex + $startTag.Length + 2, $endTagIndex - $startTagIndex - $startTag.Length - 5)
}

return $newFormDigest
}

function Nintex-GetJsonFormFromXml($FileName)
{

[System.Reflection.Assembly]::LoadWithPartialName("Nintex.Forms.SharePoint") >> $null
[System.Reflection.Assembly]::LoadWithPartialName("Nintex.Forms") >> $null

[byte[]] $fileBytes = Read-FileBytes -FileName $FileName
try
{
$form = [Nintex.Forms.FormsHelper]::XmlToObject([Nintex.Forms.NFUtilities]::ConvertByteArrayToString($fileBytes))
}
catch [Exception]
{
$form = [Nintex.Forms.FormsHelper]::XmlToObject([Nintex.Forms.NFUtilities]::ConvertByteArrayToString($fileBytes, [System.Text.Encoding]::UTF8))
}

$form.LiveSettings.Url = ""
$form.LiveSettings.ShortUrl = ""
$form.RefreshLayoutDisplayNames()
$form.Id = [guid]::NewGuid()

$json = [Nintex.Forms.FormsHelper]::ObjectToJson($form);
return $json;
}

function Import-NintexForm {
<# .SYNOPSIS Imports a Nintex form XML to a local directory. .DESCRIPTION Imports a Nintex form XML to a local directory. #>
[CmdletBinding()]
[OutputType([string])]
param(
[string]$webUrl = $(throw "Required parameter -WebUrl missing"),
[string]$listName = $(throw "Required parameter -ListName missing"),
[string]$filePath = $(throw "Required parameter -FilePath missing")
)
begin {
if(!(Get-PnPConnection)) {
throw "There is no PnPConnection"
}
Write-Host "---- Importing Nintex form for '$listName' ----" -ForegroundColor Yellow
}
process {
$list = Get-PnPList -Identity $listName

$formDigest = Get-FormDigest $webUrl
$addressUrl = $webUrl + "/_vti_bin/NintexFormsServices/NfRestService.svc/PublishForm"
$addressUri = New-Object System.Uri($addressUrl)

# Create the web request
[System.Net.HttpWebRequest] $request = [System.Net.WebRequest]::Create($addressUri)

# Add authentication to request
$request.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials

# Set type to POST
$request.Method = "POST";
$request.ContentType = "application/json; charset=utf-8";
$request.Accept = "application/json, text/javascript, */*; q=0.01"
$request.Headers.Add("X-RequestDigest", $formDigest);
$request.Headers.Add("X-Requested-With", "XMLHttpRequest")

$form = Nintex-GetJsonFormFromXml -FileName $filePath

# Create the data we want to send
$id = "{$($list.ID)}"
$data = "{`"contentTypeId`": `"`", `"listId`": `"$id`", `"form`": $form }"

# Create a byte array of the data we want to send
$utf8 = New-Object System.Text.UTF8Encoding
[byte[]] $byteData = $utf8.GetBytes($data.ToString())

# Set the content length in the request headers
$request.ContentLength = $byteData.Length;

# Write data
try {
$postStream = $request.GetRequestStream()
$postStream.Write($byteData, 0, $byteData.Length);
}
catch [Exception]{
write-host -f red $_.Exception.ToString()
}
finally {
if($postStream) { $postStream.Dispose() }
}

# Get response
try {
[System.Net.HttpWebResponse] $response = [System.Net.HttpWebResponse] $request.GetResponse()

# Get the response stream
[System.IO.StreamReader] $reader = New-Object System.IO.StreamReader($response.GetResponseStream())

try {
$strResult = $reader.ReadToEnd()
$jsonResult = ConvertFrom-Json $strResult
$jsonResult

$jsonResult.PublishFormResult.Url
$jsonResult.PublishFormResult.Version

}
catch [Exception] {
write-host -f red $_.Exception.ToString()
}
}
catch [Exception] {
write-host -f red $_.Exception.ToString()
}
finally {
if($response) { $response.Dispose() }
}
}
end{}
}

 

Run.ps1

#=========================================== Description Start ========================================= #
# Deploy from a List

# Author : Sathish Nadarajan
# Date : 03-Feb-2021
#=========================================== Description End====================================== #

# ============================================ PreRequisites Start ================================= #

#Get-Module -Name *pnp*
#Pre Req - SharePoint PnP PowerShell Version 2.25.1804.1

#=============================================PreRequisites End =============================== #

#============================================= Initial Setup Start =============================== #

cls

$Host.UI.RawUI.WindowTitle = "-- Deploy Assets --"

$StartDate = Get-Date
Write-Host -ForegroundColor White "------------------------------------"
Write-Host -ForegroundColor White "| Deploy Assets |"
Write-Host -ForegroundColor White "| Started on: $StartDate |"
Write-Host -ForegroundColor White "------------------------------------"

#Add-PSSnapin Microsoft.SharePoint.PowerShell

$LogTime = Get-Date -Format yyyy-MM-dd_hh-mm-ss

$scriptBase = split-path $SCRIPT:MyInvocation.MyCommand.Path -parent
Set-Location $scriptBase

# Create Log File Folder3
if(!(TEST-PATH ".Logs-$LogTime")){
NEW-ITEM ".Logs-$LogTime" -type Directory
}

# Assign the Log and Progress Files
$TranscriptFile = ".Logs-$LogTimeDeploy.Transcript.rtf"
try{
stop-transcript|out-null
}
catch [System.InvalidOperationException]{}
start-transcript $TranscriptFile

#============================================= Initial Setup End =============================== #

# ============================================ Setup Input Paths Start ================================= #

#connect to the SharePoint list
$sourceWebUrl = 'http://SiteCollectionURL/sites/newsite/'
$sourceListname = "MyList"

$outputFolderPath = ".Logs-$LogTime"

$targetWebUrl = 'http://TargetSiteCollectionURL/sites/newsite/'
$targetListname = "TargetMyList"

# ============================================ Setup Input Paths End ================================= #

Import-Module ".1.ExportNintexForms.ps1"
Import-Module ".2.ImportNintexForms.ps1"

Write-Host "Begin to Execute.." -ForeGroundColor Yellow
"Begin to Execute..." | Out-File -FilePath $TranscriptFile -Append

Connect-PnPOnline -Url $sourceWebUrl -CurrentCredentials -ErrorAction Inquire

$nintexFormXMLPath = Export-NintexForm $sourceWebUrl $sourceListname $outputFolderPath

Import-NintexForm $targetWebUrl $targetListname $nintexFormXMLPath

Disconnect-PnPOnline

Write-Host "Update Completed.. Press Enter to Exit" -ForeGroundColor Green

try{
stop-transcript|out-null
}
catch [System.InvalidOperationException]{}

Happy Coding
Sathish Nadarajan

Author Info

Sathish Nadarajan
 
Solution Architect
 
Rate this article
 
Sathish is a Microsoft MVP for SharePoint (Office Servers and Services) having 15+ years of experience in Microsoft Technologies. He holds a Masters Degree in Computer Aided Design and Business ...read more
 

Leave a comment