21 Nov 2022

Contributing my own .NET Core Library: Rdz.Net.Library

Good day fellas! After quite some time never in touch with .NET code for a longer period of time, it's time for me to release extension methods that I have been using, with a twist. I'm releasing in conjunction of the latest version of .NET Core so that it will be usable in every application you might want to build (Windows, Linux, Android, iOS).

I have been discussing with dotnet team in the past to consider contributing this piece of code into .NET Core Framework, and it seems that the addition raised some concerns since it most likely conflicts with the existing String.Format(String, Object[]) method. See the discussion here: API extension proposal for System.String: string.Format function for string template · Issue #33136 · dotnet/runtime (github.com).

The main reason for this method was started when I code extensively with String.Format, but always fail to remember which index the exact object is located. String.Format does need you to specify which index of the object you want to format, and in most cases if you fail to remember, you could end up in error or wrong formatting.

Please look at the following links in order for you to get started and look around if it fits you. Go to NuGet gallery (NuGet Gallery | Rdz.Net.Library 1.1120.1039.1387) or GitHub for the source code (radityoardi/Rdz.Net.Library: It's a lightweight .NET library to simplify your code. (github.com)).

Cheers...!

Writing .NET Core Class Library with Automatic Versioning

When you want to start working on .NET Core Class Library, the first thing you would want to worry about is the attributes of the project you're working on. One of those things is versioning. It's always been a complicated ordeal because typically you want to version your work automatically.

There are many ways to version your class library, from utilizing the CI/CD components, or as simple as empowering your project file to do that for you automatically. This post will help you with the latter.

Creating .NET Core Class Library Project

To create a .NET Core Class Library project, you can follow the PowerShell commands below.

md dotnet-lib
cd .\dotnet-lib\
dotnet new sln --name dotnet-lib
dotnet new classlib --name MyLibrary --framework net6.0 --language C#
dotnet sln add MyLibrary\MyLibrary.csproj

Those commands will get you up quite easily in terms of creating the project, or you can use Visual Studio of any version (Community, Enterprise, or any latest versions).

Add Version Logic into the Project File

Once you're done with creating, now it's time to modify the project file. Now, here's something that's debatable in the sense of how you want to version your project. One might say you shouldn't version based on the date and time and stick with the traditional way of versioning, but I wouldn't mind if that will keep me focus on what I should do.

//standard versioning
1.2.4039
//{manual-major}.{manual-minor}.{build-count}

//date and time versioning
2022.11.21.1159
//{year}.{month}.{day}.{hour-minute}

In a real-world scenario, some people work on multi-projects that could take away our focus that would end up giving the wrong version, that's total madness. But each to their opinion by the way, and you should take a part when discussing versions about to take place.

Edit your MyLibrary\MyLibrary.csproj file, you should see something similar to the below code.



  
    net6.0
    enable
    enable
  


Still inside the Project element & below the PropertyGroup, add the following code.

What this UsingTask element does is to create a custom code function within your project file, and that function will return the auto-generated version as an output. So, to make things clearer, the task name in this example is SetVersionNumber that accepts 1 input parameter of CreationYear and 1 output parameter of GeneratedVersion.

As we put this task, it won't do anything until we attach the task to certain build targets. put the following code just below that UsingTask element.

$(GeneratedVersion)

Now, this Target element does execute sequentially of all elements within. There are 3 major sections of elements: executing SetVersionNumber task and output as GeneratedVersion variable, then output a Message to the build console, and then finally set the different version artifacts from GeneratedVersion variable.

That's it!

Your final code of csproj file may look like this below.

And your generated Class Library after build will look like this below.

Conclusions

This is definitely one of the ways, the easier and you don't need to work too much to define your version. You just need to set this once and forget for all other times, gives you more focus on what you're doing.

I'll talk more about how you can combine this with NuGet packaging and publishing via GitHub Actions.

29 Agu 2022

Why SharePoint Can't Be Hosted on Azure App Service?

I've been working with SharePoint in more than half of my entire career since SharePoint 2003, that is now after the Online version is getting more recognized by organizations, sometimes we get questions from those who are very much expert in the on-premises version but unaware of the features of Online one. This is understandable because some organizations still want to keep their data on-premises and possibly not ready to move to the cloud becoming unaware of Microsoft cloud environment.

Now, a question in particular was about whether or not SharePoint can be migrated to Azure App Service, and if not, is there any particular documentation from Microsoft that clearly states that it can't be done.

This post hope to help anyone trying to understand why it can’t be done, in my personal opinion.

I did my little research - which I know it can't be done - to find any explicit statement from Microsoft docs that says so. Unfortunately, as I thought before, I couldn't find any. Or maybe somewhere under the rock. Okay, so I have to explain on what is Azure App Service and what are needed in order to run SharePoint any where.

In a nutshell, SharePoint can’t run on Azure App Service because of the complex services it serves, as most of these services aren’t just simply HTTP just like a web application/web service.

Azure App Service essentially is exactly the same like your typical web application sitting in your IIS in Windows Server or Apache in Linux. It serves files stored under IIS folder such as HTML, JavaScript, or compiled .NET code as DLL to be served via HTTP/S protocol. From the infrastructure perspective, Azure App Service basically same like your Windows Server, or Linux. One big advantage of using the App Service is that you don’t need to care the server configuration, setting up join domain, authentication provider, ports, so you can focus on building the application you like and serve it directly. Just like staying in a hotel, you just come, pay, and lie down literally.

Of course come to the disadvantage, you can’t roam freely to the kitchen where the chefs work, you can’t force the hotel appearance and ambience you like, or sleeping in the reception area much like your living room, much towards altering the entire hotel itself. App Service doesn’t allow you to install Window Service, dictate how many IIS sites you need to add, or connect to other servers you want. Your space is only that little tiny folder assigned to you via Azure App Service.

To take on the same analogy, SharePoint on the other hand, is not just you moving in but also your furniture, electronics, appliances, cupboard, and kitchen area that has a very specific requirements to operate it.

For instance, SharePoint User Profiles that is used to crawl users in your Active Directory, Timer Jobs that runs essential scheduled job for SharePoint, not to mention SharePoint Search to crawl the content of this SharePoint. These are under Window Service and served via particular ports and consumed by other SharePoint servers in the same farm.

For SharePoint SQL Server database however, you have the option to use Azure SQL Managed Instance (MI), with caveats. Forget about Azure SQL MI if your SharePoint farm was configured using Windows Authentication, but you can if it’s SQL Authentication. I saw some articles too that you can convert SharePoint database from Windows authentication to SQL, but let’s not talk about this for now.

What’s feasible then if you ask? SharePoint on Azure is the answer, not via App Service but the traditional Virtual Machine (VM). Lift and shift, load your existing VM to Azure (require downtime), then set your networking properly and make sure the servers can communicate each other, and ensure connection to Domain Controller is also established.

With this explanation, you will not get any complain from the hotel for bringing your own fridge and kitchen appliances down to hotel because you can’t do so. 😉

References:

27 Agu 2022

Top 4 Fundamental Knowledge To Master Power Automate

Now you want to learn Power Automate but you have no idea where to start? Below are the keys to mastering Power Automate in no time!


You can go to Microsoft Learn to take on self-paced courses on dealing with Power Automate in Get started with Power Automate - Learn | Microsoft Docs. But before that, here are the top 4 fundamental topics you need to master before jumping into Power Automate.

1. JSON

JSON, short for JavaScript Object Notation, is merely a lightweight data transportation format. Quoted from json.org, JSON is easy for humans to read and write. Mainly because the data structure is simple but can fit into different types of data. Imagine JSON is water, very fluid, and can fit into many different containers. The entire IT standards are now revolving around JSON as the format of the data transportation from one system to another.

In Power Automate, JSON plays a crucial element when coming to developing a system. Often we face an error while developing and it's important to pinpoint the core of the issue before we take action to fix it.


Power Automate, even the entire suite of Microsoft Azure products and Office 365, were built around JSON as the data payload. Calling a SharePoint site with API, getting data from Dataverse, and even working with NoSQL databases such as CosmosDB, they're using JSON. Below is an example.

{
  "glossary": {
    "title": "example glossary",
    "GlossDiv": {
      "title": "S",
      "GlossList": {
        "GlossEntry": {
          "ID": "SGML",
          "SortAs": "SGML",
          "GlossTerm": "Standard Generalized Markup Language",
          "Acronym": "SGML",
          "Abbrev": "ISO 8879:1986",
          "GlossDef": {
            "para": "A meta-markup language, used to create markup languages such as DocBook.",
            "GlossSeeAlso": [
              "GML",
              "XML"
            ]
          },
          "GlossSee": "markup"
        }
      }
    }
  }
}
My only reference for learning about JSON is from json.org.

2. Expression

You must also learn how to make Power Automate expression, which is essentially a code you call to get the value you want in every part of Power Automate. If you already know Power Automate, oftentimes you get data directly from the Dynamic content just like below.


But here's a secret, NOT ALL data are presented in the dynamic content. You sometimes have to run the flow to get the precedent actions/trigger data presented in JSON format, and then consume it.


  triggerOutputs()['headers']['x-ms-user-name-encoded']
  

The example above is the code for getting data from the Power Automate trigger, of the username in encoded format.


What if you want to check if the trigger came from a specific IP address that is stored in X-Forwarded-For header? Well, it's the same process.

  triggerOutputs()['headers']['X-Forwarded-For']
  

Now, what if you want to get a value that sometimes exists, but another time doesn't? Just put a question mark in between.


  triggerOutputs()['headers']?['X-Forwarded-For']
  

By giving a question mark, your code will not turn into an error if in the future X-Forwarded-For is not always there, the value returned will be null or empty.



3. OData

Now you know the data is transported using JSON format, how did it get transported? Some systems - if not, every system nowadays - are transported via an API, essentially a web service that you can call using a specific URL. OData itself is a standard most organizations follow including Microsoft, to build RESTful API.

SharePoint Online and Microsoft Dataverse use OData standards to communicate or get the data. This includes all administrative tasks such as get site collection, get environment (of Dataverse), and even the underlying Power Automate designer tool when you are working also uses OData standards. You can try pressing F12 in your browser, open up a Power Automate designer, and see it under the Network tab. That's a whole bunch of OData.


What makes OData unique is the ability to select, filter, expand, and sort, and of course it all depends on whether the vendor conforms to the OData standard. My go-to place to learn about OData is always SharePoint Online, with the reference from Get to know the SharePoint REST service | Microsoft Docs. If you don't have one, you can create an Office 365 Developer tenant via this link Developer Program | Microsoft 365 Dev Center.

Now, you can use Chrome or Edge (whichever you like), and install ModHeader - Chrome Web Store (google.com). This tool helps you to send an HTTP Web Request header along with your URL query. You need this tool because SharePoint Online uses XML to transport the data.

Set the request header just like above, and you're good to go.

4. HTTP Web Request

Knowing the concept of HTTP Web Request is also crucial to better understand how things work in Power Automate. Every single action in Power Automate is essentially a block that will run HTTP Web Request depending on how it was developed.

Most importantly, the concept of different types of requests, to understand how to set a setting of your HTTP Web Request in the Request Header or Body, to retrieve the data you want according you your specification.

8 Mei 2022

Spider Workflow in Action

After my previous post about the conception of the spider workflow, let’s put this into action. Let’s think about multi-level of approvals that you might have in your requirement.

Let’s imagine we have this simple requirement for a workflow in Power Automate, and we need to express the logic in Power Automate using the spider workflow concept. First of all, let’s work the trigger header, because we might need some of the information in it especially which account triggered the workflow.

Next, let’s work on the variables. Like previously mentioned, we need a boolean variable which will be the stopper of the workflow just like a cork of your wine bottle. Next is the entire workflow configuration just like I have shown you earlier (read the previous post for more) but with a little twist.

If you want a quick escape, just copy and paste the following code below.



[
  {
    "Index": 1,
    "Stage": "Review",
    "Status": "Pending Project Admin Review",
    "Type": "CustomApproval",
    "Approval": {
      "Actions": [
        "Checked",
        "Return"
      ]
    },
    "SendAttachment": true,
    "AssignedTo": {
      "AssignmentType": "Custom",
      "Assignee": [
        "radityo.ardi@radityoardidev.onmicrosoft.com"
      ]
    },
    "Outcomes": [
      {
        "Outcome": "Checked",
        "Next Index": 2
      },
      {
        "Outcome": "Return",
        "Next Index": 7
      }
    ]
  },
  {
    "Index": 2,
    "Stage": "Approval 1",
    "Status": "Pending Approver 1",
    "Type": "BasicApproval",
    "SendAttachment": true,
    "AssignedTo": {
      "AssignmentType": "Custom",
      "Assignee": [
        "radityo.ardi@radityoardidev.onmicrosoft.com"
      ]
    },
    "Outcomes": [
      {
        "Outcome": "Approve",
        "Next Index": 3
      },
      {
        "Outcome": "Reject",
        "Next Index": 6
      }
    ]
  },
  {
    "Index": 3,
    "Stage": "Approval 2",
    "Status": "Pending Approver 2",
    "Type": "BasicApproval",
    "SendAttachment": true,
    "AssignedTo": {
      "AssignmentType": "Custom",
      "Assignee": [
        "radityo.ardi@radityoardidev.onmicrosoft.com"
      ]
    },
    "Outcomes": [
      {
        "Outcome": "Approve",
        "Next Index": 4
      },
      {
        "Outcome": "Reject",
        "Next Index": 6
      }
    ]
  },
  {
    "Index": 4,
    "Stage": "Super Admin",
    "Status": "Pending Super Admin",
    "Type": "CustomApproval",
    "Approval": {
      "ApprovalType": "customresponse",
      "Actions": [
        "Complete Request",
        "Return"
      ]
    },
    "SendAttachment": true,
    "AssignedTo": {
      "AssignmentType": "Custom",
      "Assignee": [
        "radityo.ardi@radityoardidev.onmicrosoft.com"
      ]
    },
    "Outcomes": [
      {
        "Outcome": "Complete Request",
        "Next Index": 5
      },
      {
        "Outcome": "Return",
        "Next Index": 7
      }
    ]
  },
  {
    "Index": 5,
    "Stage": "Closed",
    "Status": "Completed",
    "Type": "Closure"
  },
  {
    "Index": 6,
    "Stage": "Closed",
    "Status": "Rejected",
    "Type": "Closure"
  },
  {
    "Index": 7,
    "Stage": "Resubmission",
    "Status": "Pending Resubmission",
    "Type": "Closure",
    "SendAttachment": true
  }
]


Next up, is to parse the JSON configuration well enough so that it’ll save time when you want to reference a specific JSON object property. And the subsequent action is to initialize the first index we need to tag as the first entry of the stages.

Just for your own sake, Index is just one idea to ID the current workflow stage. In the real world, you can use string, GUID, text, numbers, or anything that can identify what is the current stage and it has to be unique.

Now, here’s the heart of the spider workflow. We can’t use a state, pointer, go-to, or something like that in Power Automate to make the flow goes back to the controller. To overcome, we use Do-Until action until the [Keep Looping] variable turns false. You need the [Stop the loop] in case of any error or time out, so it doesn’t go to endless loop. Optionally, you can get the controller’s error message.

[Stop the loop] basically just to set [Keep Looping] to false.

Inside the controller, you probably need to find what is the current stage from the configuration. The next step up, is to filter the parsed body of the entire configuration that come out from [Parse Flow Configuration] action. This is where it gets interesting, you have to filter where the [Index] is equal to [Stage Index] which is 1. Then, parse the JSON value simply by taking the body of [Filter Flow Configuration on Index] which will return an array and take the first one.

Parsing the JSON is always helpful especially when you want to work the flow quickly without doing too many trial-and-error.

But then the next action is just simply notify the requestor what’s the current stage coming up. It’ll be just a message saying “Hi requestor, your flow now is in ___ stage with status of ____”. Simple enough…

Then, you [Switch on Type] that will be the path of your next bus stop of your flow. In this case, we identify 3 different types based on the configuration: BasicApproval, CustomApproval, and Closure. We can put [Closure] under [Default] which makes the logic more broad as if like “if the type is either Closure or everything else, then stop the loop”. Let’s take a closer look how the routing done on approval side.

Now you clearly see, that we’re going to filter the [Outcomes] array where [Outcome] property is equal to the outcome of the flow and return what’s the [Next Index] of that outcome and assign back to the [Stage Index]. Eventually, the loop will be executed once again to see whether it’s the Closure or another step of approval.

No bluff, that’s it!

Last words

Now, all I can say is just congratulations that you now can work with any kind of approval flow. Your stakeholder asks for more approval? No problem, give ‘em that!

But just don’t be too happy first and hear me out. If your workflow configuration is salty, the entire workflow may go ugly too. So, make sure you have done your thought process while thinking out loud what’d be the configuration for this flow.

via GIPHY

5 Mei 2022

Implementing Spider Workflow in Power Automate

There are many patterns to implement different types of workflow using Power Automate. But one that strikes me a lot in terms of the dynamicity, is the spider workflow. There are not much of reference out there, it was (in my work history) implemented in a project in one of the prestigious client in Singapore, sparked by my technical architect.

Drawing1

It’s called spider because… of course it looks like a spider. This pattern can be repeated in many projects and even one can implement the template for it in Power Automate. This pattern mainly to overcome a problem when the workflow is not well-defined, prone to change, or to provide dynamic ever-changing workflow without re-work of the entire workflow.

Implementing Spider Workflow

The main concept of this workflow is to loop the process back to the controller after the decision of the next path. The first step to work this out, is to define how many distinctive tasks that a process can do. In the sample diagram above, there are 5 paths that goes everywhere with one of them proceed to end the workflow. The other four, goes back to the controller to decide what’s the next path after an outcome is reached. In the real-world, it can spawn more than 5 or less, depending on the requirement.

The second step, you need to run your thought process to link up between an outcome with the next path. To give you an example, if say the initial flow goes to Approval first (Path 1), definitely it will have 2 outcomes whether it’s Approve or Reject. You could make a story where after the first approval, it will go to another level of approval if the outcome is Approve (Path 1). Otherwise if it’s Reject, you then decide to flow it to End the workflow (Path 3). If you get the feel of your own thought process thoroughly, you could end up having the exact same flow like in the diagram below.

Drawing2

Now, how do we take care of the workflow’s most important mechanism such as notifying the requestor or setting the workflow status once it’s completing one stage? These mechanism you actually can put under the controller itself since it’s executed every time it passes with another path decision.

Be very careful when you run your thought process, do avoid any process that potentially goes into an endless loop (because it can happen). The design itself is perfect, this endless loop can happen due to misconfiguration. Wait, there’s configuration?

Spider Workflow Configuration

There is a configuration to accompany this flow. You could actually leverage on anything to store, literally. Back in my old days, all workflow data are stored as a row in a SQL Server database. Nowadays, you can just work out the JSON configuration and let the configuration be parsed by the flow. Let’s take a sample on how you can define the JSON structure for just one single stage.


{
	"Index": 2,
	"Stage": "Approval 1",
	"Status": "Pending Approver 1",
	"Type": "BasicApproval",
	"SendAttachment": true,
	"AssignedTo": "Dynamic",
	"Outcomes": [
		{
			"Outcome": "Approve",
			"Next Index": 3
		},
		{
			"Outcome": "Reject",
			"Next Index": 6
		}
	]
}

As above is just a simple sample, you could define your own JSON that depicts the flow process. To summarize,

  • Index – defines the index of this current path.
  • Stage – defines the current stage name (or whatever you want to define as the state of the current flow).
  • Status – defines the workflow status for the current path.
  • Type – defines the path where to go. If you could link this up with the first diagram above, it’s the Path 1.
  • SendAttachment – typically the current controller’s configuration to send attachment to the approver. You can also add your own other configuration.
  • AssignedTo – is also the current controller’s configuration to take the approver list dynamically from a table or database. The other values can also be “Requestor” where it will assign to the initiator of the flow.
  • Outcomes – defines the routing! You can read it simply by say if the outcome is “Approve”, go to the object that the index is 3. Else if it’s “Reject”, go to index 6.

Now, if you run your thought process, you could define a JSON configuration for the entire process just like below.


[
  {
    "Index": 0,
    "Stage": "Resubmission",
    "Status": "Pending Resubmission",
    "Type": "Closure",
    "SendAttachment": true,
    "AssignedTo": "Requestor"
  },
  {
    "Index": 1,
    "Stage": "Review",
    "Status": "Pending Project Admin Review",
    "Type": "CustomApproval",
    "SendAttachment": true,
    "Actions": [
      "Checked",
      "Return"
    ],
    "AssignedTo": "Dynamic",
    "Outcomes": [
      {
        "Outcome": "Checked",
        "Next Index": 2
      },
      {
        "Outcome": "Return",
        "Next Index": 0
      }
    ]
  },
  {
    "Index": 2,
    "Stage": "Approval 1",
    "Status": "Pending Approver 1",
    "Type": "BasicApproval",
    "SendAttachment": true,
    "AssignedTo": "Dynamic",
    "Outcomes": [
      {
        "Outcome": "Approve",
        "Next Index": 3
      },
      {
        "Outcome": "Reject",
        "Next Index": 6
      }
    ]
  },
  {
    "Index": 3,
    "Stage": "Approval 2",
    "Status": "Pending Approver 2",
    "Type": "BasicApproval",
    "SendAttachment": true,
    "AssignedTo": "Dynamic",
    "Outcomes": [
      {
        "Outcome": "Approve",
        "Next Index": 4
      },
      {
        "Outcome": "Reject",
        "Next Index": 6
      }
    ]
  },
  {
    "Index": 4,
    "Stage": "Super Admin",
    "Status": "Pending Super Admin",
    "Type": "CustomApproval",
    "SendAttachment": true,
    "Actions": [
      "Complete Request"
    ],
    "AssignedTo": "Dynamic",
    "Outcomes": [
      {
        "Outcome": "Complete Request",
        "Next Index": 5
      }
    ]
  },
  {
    "Index": 5,
    "Stage": "Closed",
    "Status": "Completed",
    "Type": "Closure"
  },
  {
    "Index": 6,
    "Stage": "Closed",
    "Status": "Rejected",
    "Type": "Closure"
  }
]


Lastly, this blog is just a conceptual idea that you can implement in each project you go on either with Nintex, Power Automate, K2, or any other cloud workflow out there.

16 Nov 2018

Add PowerShell Context Menu to Windows Explorer

Do you want to get this in your laptop?

image

Here is what you should do. Open regedit.

image

Navigate to HKEY_CLASSES_ROOT\Directory. You will need to focus on 2 keys, Background\shell and shell alone.

image

Background\shell basically to control when you are right-clicking at nothing inside a folder.

D1AF38B4-E1D5-41E8-8E0D-32837C4AE45A

Lastly, you can copy the code below and save it with .reg extension.


Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere]
@="Open PowerShell Here"
"Icon"="%SystemRoot%\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"

[HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere\command]
@="powershell.exe -noexit"

[HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere]
@="Open PowerShell Here"
"Icon"="%SystemRoot%\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"

[HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere\command]
@="powershell.exe -noexit -command Set-Location '%V'"

10 Apr 2018

Connecting to Microsoft OAuth 2.0

Connecting to OAuth 2.0 authentication might be a common and critical task to do for many developers, especially in Microsoft Office 365 environment. It could be a connection to OneDrive, Excel, SharePoint, OneNote, even the big developer platform such as Microsoft Graph. It could be connecting to any other cloud app services such as twitter, facebook, google, pinterest, or any other cloud services in this world.

You can take a look to find out how we deal with Microsoft OAuth 2.0 authorization code is retrieved and how the flow works for you at: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-code.

In practical way, you can follow my step-by-step below if you find the article is not clear enough or lack of examples. This article will be focusing on Microsoft Online credential which can be used with Azure Active Directory or Microsoft Online account.

finger-769300_640


Registering Your App

Before you can make connection to any cloud services using OAuth 2.0, you would need 2 things, Client ID and Client Secret. Anything that connects using OAuth 2.0 is considered an app, and you need to register that before proceeding. This will usually generates you a Client ID and commonly with Client Secret, which acts same like your own username and password.

15 Agu 2017

PowerShell Script: List Files and Sub-Folders in a SharePoint Document Library

Just want to share to you guys for today, which I hope it will help you out when you need it. I’ve been writing a few about PowerShell before, PowerShell Script upload large file. Here is the script to list all files and sub-folders in a SharePoint Document Library.

Syntax:
.\SPDocContent.ps1 [[-Credential] <PSCredential>] [-URL] <String> [-DocumentLibrary] <String> [[-BaseFolderPath] <String>] [[-CSVPath] <String>] [<CommonParameters>]



##############################################################################
#.SYNOPSIS
# Gets a list of files, folders and sub-folders in a SharePoint Online Document Library.
#
#.DESCRIPTION
# Gets a list of files, folders and sub-folders in a SharePoint Online Document Library.
#
#.PARAMETER Credential
# The credential to access with SharePoint Online.
#
#.PARAMETER URL
# The URL of the SharePoint site (can be a sub-site or root site).
#
#.PARAMETER Document Library
# The Document Library name to connect to.
#
#.PARAMETER BaseFolderPath
# Start from particular sub-folder (optional).
#
#.PARAMETER CSVPath
# The path of the CSV file to export the result.
#
#.EXAMPLE
# $Crd = Get-Credential
# .\SPDocContent.ps1 -URL "https://rdz.sharepoint.com" -DocumentLibrary "Documents" -CSVPath "D:\Documents.csv"
##############################################################################

[Cmdletbinding()]
param (
	[Parameter(Mandatory = $false)]
    [System.Management.Automation.PSCredential]$Credential = $null,
    [Parameter(Mandatory=$true)]
    [string]$URL,
    [Parameter(Mandatory=$true)]
    [string]$DocumentLibrary,
    [Parameter(Mandatory=$false)]
    [string]$BaseFolderPath,
	[Parameter(Mandatory = $false)]
    [string]$CSVPath = ""
)

Function ListContent(
    [Parameter(Mandatory=$true)]
    [Microsoft.SharePoint.Client.Folder]$BaseFolder,
    [Parameter(Mandatory=$false)]
    [switch]$Recursive = $false,
    [Parameter(Mandatory=$false)]
    [Microsoft.SharePoint.Client.Folder]$RootFolder,
    [Parameter(Mandatory=$false)]
    [int]$Level = 0
)
{
    $ContentInfo = @()

    $RootLevel = $false;
    $ctx.Load($BaseFolder.Folders)
    $ctx.Load($BaseFolder.Files)
    $ctx.Load($BaseFolder.ParentFolder)
    $ctx.ExecuteQuery()

    if ($RootFolder -eq $null) { $RootLevel = $true; $RootFolder = $BaseFolder; }
    $tabsRepeat = New-Object System.String("`t", ($Level + 1))

    if ($BaseFolder.Folders.Count -gt 0)
    {
        Write-Host "$($tabsRepeat)$($BaseFolder.Folders.Count) sub-folders for $($BaseFolder.ServerRelativeUrl.Replace($RootFolder.ServerRelativeUrl + "/", [System.String]::Empty))" -ForegroundColor Yellow
        for ($i = 0; $i -lt $BaseFolder.Folders.Count; $i++)
        {
            $ctx.Load($BaseFolder.Folders[$i])
            $ctx.ExecuteQuery()
            if ($BaseFolder.Folders[$i])
            {
                if (($RootLevel -and $BaseFolder.Folders[$i].Name -ne "Forms") -or -not $RootLevel)
                {
                    Write-Host "$($tabsRepeat)$($BaseFolder.Folders[$i].ServerRelativeUrl.Replace($RootFolder.ServerRelativeUrl + "/", [System.String]::Empty))"
                    $ContentInfo += [pscustomobject]@{
                        RelativePath = "$($BaseFolder.Folders[$i].ServerRelativeUrl.Replace($RootFolder.ServerRelativeUrl + "/", [System.String]::Empty))";
                        Type = "Folder";
                        Name = "$($BaseFolder.Folders[$i].Name)";
                        Level = $Level;
                    };

                    if ($Recursive -eq $true)
                    {
                        $ContentInfo += (ListContent -BaseFolder $BaseFolder.Folders[$i] -RootFolder $RootFolder -Recursive:$Recursive -Level ($Level + 1))
                    }
                }
            }
        }
    }
    if ($BaseFolder.Files.Count -gt 0)
    {
        Write-Host "$($tabsRepeat)$($BaseFolder.Files.Count) sub-files for $($BaseFolder.ServerRelativeUrl.Replace($RootFolder.ServerRelativeUrl + "/", [System.String]::Empty))" -ForegroundColor Yellow
        for ($i = 0; $i -lt $BaseFolder.Files.Count; $i++)
        {
            $ctx.Load($BaseFolder.Files[$i])
            $ctx.ExecuteQuery()
            if ($BaseFolder.Files[$i])
            {
                $ContentInfo += [pscustomobject]@{
                    RelativePath = "$($BaseFolder.Files[$i].ServerRelativeUrl.Replace($RootFolder.ServerRelativeUrl + "/", [System.String]::Empty))";
                    Type = "File";
                    Name = "$($BaseFolder.Files[$i].Name)";
                    Level = $Level;
                };
                Write-Host "$($tabsRepeat)$($BaseFolder.Files[$i].ServerRelativeUrl.Replace($RootFolder.ServerRelativeUrl + "/", [System.String]::Empty))"
            }
        }
    }
    return $ContentInfo;
}

$ContentInfo = @()
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($URL)
Try
{
    if ($Credential)
    {
        if ($Credential.GetType().FullName -eq "Microsoft.SharePoint.Client.SharePointOnlineCredentials")
        {
            $ctx.Credentials = $Credential
        } elseif ($Credential.GetType().FullName -eq "System.Management.Automation.PSCredential")
        {
            $ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Credential.Username,$Credential.Password)
        }
    }
    elseif ($PSCrd)
    {
        Write-Host "No credential set, retrieving from `$PSCrd." -ForegroundColor Yellow
        $ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($PSCrd.Username,$PSCrd.Password)
    }
    elseif ($SPCrd)
    {
        Write-Host "No credential set, retrieving from `$SPCrd." -ForegroundColor Yellow
        $ctx.Credentials = $SPCrd
    }
    else
    {
        Write-Host "No credential set, none retrieved." -ForegroundColor Red -BackgroundColor Black
        exit
    }

    $Site = $ctx.Site
    $Web = $ctx.Web
    $ctx.Load($Site)
    $ctx.Load($Web)
    $ctx.Load($Web.Lists)
    $ctx.Load($Web.Webs)
    $ctx.ExecuteQuery()

    $DL = $Web.Lists.GetByTitle($DocumentLibrary)
    $ctx.ExecuteQuery()

    $ctx.Load($DL.RootFolder)
    $ctx.ExecuteQuery()

    if ($BaseFolderPath)
    {
        $RootFolderPath = "$($DL.RootFolder.ServerRelativeUrl)/$($BaseFolderPath)"
        $RootFolder = $DL.RootFolder.Folders.GetByUrl($BaseFolderPath)
        $ctx.Load($RootFolder)
        $ctx.ExecuteQuery()
        $ContentInfo += (ListContent -BaseFolder $RootFolder -Recursive)
    }
    else
    {
        $ContentInfo += (ListContent -BaseFolder $DL.RootFolder -Recursive)
    }

    if ($CSVPath)
    {
        if (Test-Path $CSVPath) { Remove-Item $CSVPath }
        $ContentInfo | Export-Csv $CSVPath -NoTypeInformation
    }
}
Catch
{
    Throw
}
Finally
{
    if ($ctx -ne $null) { $ctx.Dispose(); }
}

Note: this script is only meant for SharePoint Online.

13 Jun 2017

PowerShell Script: Upload Large File to SharePoint in Chunks

Get back to the business again! Just wanted to share another great things in SharePoint (although to user is not fun at all).

According to MSDN, there are a few options to upload files to SharePoint.