The Role of a CISO in Building a Modern Cybersecurity Culture

Mr Josef Flügel from Germany – CISO (Chief Information Security Officer) at Lenze Group, who visited Pune in the month of January shared his knowledge and experience with us on developing and implementing an organization’s information security program.

He explained that the role of a Chief Information Security Officer (CISO) is to oversee the organization’s information security program and ensure the confidentiality, integrity, and availability of its information assets. The CISO is responsible for identifying, assessing, and managing information security risks, developing and implementing security policies and procedures, and ensuring compliance with relevant laws and regulations.

Specifically, some of the key responsibilities of a CISO may include:

  1. Developing and implementing an information security strategy and roadmap
  2. Establishing and maintaining an information security governance framework
  3. Conducting risk assessments and developing risk mitigation plans
  4. Developing and implementing security policies, standards, procedures, and guidelines
  5. Conducting security awareness training and education programs for employees
  6. Overseeing the implementation of security controls and technologies
  7. Establishing incident response plans and procedures
  8. Conducting regular security assessments and audits
  9. Maintaining relationships with internal stakeholders and external partners to promote security awareness and best practices
  10. Reporting on the organization’s security posture to executive leadership and the board of directors.

In summary, the CISO plays a critical role in protecting an organization’s information assets and maintaining the confidentiality, integrity, and availability of sensitive data.

Visual ChatGPT

Visual ChatGPT is a version of the ChatGPT language model that has been trained to generate text-based responses to visual prompts. It uses computer vision algorithms to analyze and interpret images, and then generates text based on the content of the image.

To get started with Visual ChatGPT, you will need to provide it with an image that you want to generate a response for. This can be done by either uploading an image file or providing a URL to an image hosted online.

Once the image has been processed by the computer vision algorithms, Visual ChatGPT will generate a response in natural language based on the content of the image. This response can be used to answer questions, provide information, or generate captions for images.

To use Visual ChatGPT, you can interact with it through a chat interface, similar to how you would interact with a human chatbot. Simply provide the image and wait for Visual ChatGPT to generate a response. You can also provide additional context or information to help guide the response generated by the model.

Enable Visual ChatGPT on your Windows Machine

Folow the steps:

# clone the repo
git clone https://github.com/microsoft/visual-chatgpt.git

# Go to directory
cd visual-chatgpt

# download Ananconda & create a new environment
https://www.anaconda.com/products/distribution

conda create -n visgpt python=3.8

# activate the new environment
conda activate visgpt

#  prepare the basic environments
pip install -r requirements.txt

# Generate an API Key/Secret Key from your OpenAi.com 
(see image below)

# set your private OpenAI key (for Windows)
set OPENAI_API_KEY={Your_Private_Openai_Key}
set OPENAI_API_KEY=sk-...

# Start Visual ChatGPT !
# You can specify the GPU/CPU assignment by "--load", the parameter indicates which 
# Visual Foundation Model to use and where it will be loaded to
# The model and device are separated by underline '_', the different models are separated by comma ','
# The available Visual Foundation Models can be found in the following table
# For example, if you want to load ImageCaptioning to cpu and Text2Image to cuda:0
# You can use: "ImageCaptioning_cpu,Text2Image_cuda:0"

# Advice for CPU Users
python visual_chatgpt.py --load ImageCaptioning_cpu,Text2Image_cpu

# Advice for 1 Tesla T4 15GB  (Google Colab)                       
python visual_chatgpt.py --load "ImageCaptioning_cuda:0,Text2Image_cuda:0"
                                
# Advice for 4 Tesla V100 32GB                            
python visual_chatgpt.py --load "ImageCaptioning_cuda:0,ImageEditing_cuda:0,
    Text2Image_cuda:1,Image2Canny_cpu,CannyText2Image_cuda:1,
    Image2Depth_cpu,DepthText2Image_cuda:1,VisualQuestionAnswering_cuda:2,
    InstructPix2Pix_cuda:2,Image2Scribble_cpu,ScribbleText2Image_cuda:2,
    Image2Seg_cpu,SegText2Image_cuda:2,Image2Pose_cpu,PoseText2Image_cuda:2,
    Image2Hed_cpu,HedText2Image_cuda:3,Image2Normal_cpu,
    NormalText2Image_cuda:3,Image2Line_cpu,LineText2Image_cuda:3"
OpenAI – API Keys

Demo

The Visual ChatGPT demo should now be running on your local machine. You can open a web browser and navigate to http://localhost:7868 to interact with the model.

It’s important to note that while Visual ChatGPT can generate responses based on visual prompts, it is not perfect and may sometimes generate inaccurate or inappropriate responses. As with any AI model, it is important to use it responsibly and carefully evaluate the accuracy of its responses.

Cheers!

Reference: https://github.com/microsoft/visual-chatgpt

Congratulations 2023 Microsoft MVP!

It is a great honour for me to receive this MVP award, this is an incredible moment and one of the most significant milestones in my professional career.

I want to thank all who have contributed to this success starting with Foyin Olajide-Bello for nominating me for this program; Dr Rolf Rettinger whose inspiration has been most valuable; Patrick Guimonet & Mohamed Amar ATHIE encouraged me along and were always there for support – also all those who have helped me, directly or indirectly.

Furthermore, I am humbly thankful to the MVP Award Team who selected me qualified for this award.

Finally, my utmost respects go to the whole fraternity whose support has enabled me to make it all the way.

Upload Document in SAP Easy DMS using ODATA, SharePoint Online & Microsoft Power Automate

We got an interesting scenario where a product manager wanted to store documents in an SAP Easy DMS.

SAP Easy DMS is a Document Management System that tracks, manages, and stores documents.

Power Automate is a service that helps you create automated workflows between your favourite apps and services to synchronise files, get notifications, collect data, and more.

As we have no direct connectors from Power Automate to SAP Easy DMS, hence we had to build our own ODATA Services in SAP Abap and consume it from Power Automate.

Let us get started

Steps to Create OData to Upload file in SAP

1. Create a structure with field type string to capture data in ODATA service with other required fields as shown below.

2. Create OData service in SEGW using the above Structure (Entity Types & Entity Set). Generate and Activate Services.

3. Redefine Create Method and use the below code sequence.

4. Read data into method

5. Convert Base 64 input data into Xtring

6. Convert XSTRING_TO_BINARY

7. Create document using FM BAPI_DOCUMENT_CREATE2

8. Once document creation is successful use FM CVAPI_DOC_CHECKIN to upload file in Document.

some sample code

DATA : lv_xstring     TYPE xstring,
           lv_op_length   TYPE i,
           lv_doctype     TYPE dokar,
           lv_docnumber   TYPE doknr,
           lv_docpart     TYPE doktl_d,
           lv_docversion  TYPE dokvr,
           ls_return      TYPE bapiret2,
           ls_entity      TYPE zst_dms_doc_upload,
           ls_docdata     TYPE bapi_doc_draw2,
           ls_objectdescr TYPE bapi_doc_drat,
           lt_objectdescr TYPE TABLE OF bapi_doc_drat,
           ls_pdf_checkin TYPE drao,
           lt_pdf_checkin TYPE STANDARD TABLE OF drao,
           ls_files       TYPE cvapi_doc_file,
           lt_files       TYPE STANDARD TABLE OF cvapi_doc_file,
           lt_content     TYPE STANDARD TABLE OF orblk WITH DEFAULT KEY,
           ls_msg         TYPE messages,
           ls_api_control TYPE cvapi_api_control.

    io_data_provider->read_entry_data( IMPORTING es_data = ls_entity ).

    CALL FUNCTION 'SCMS_BASE64_DECODE_STR'
      EXPORTING
        input  = ls_entity-file_content
      IMPORTING
        output = lv_xstring
      EXCEPTIONS
        failed = 1
        OTHERS = 2.

    CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
      EXPORTING
        buffer        = lv_xstring
      IMPORTING
        output_length = lv_op_length
      TABLES
        binary_tab    = lt_content.

    " SET VALUES
    ls_docdata-documenttype     = 'Mention Document Type'.
    ls_docdata-documentversion  = '00'.
    ls_docdata-documentpart     = '000'.
    ls_docdata-laboratory       = '000 '.

    ls_objectdescr-description  = 'Test Document Aditya' && ':' && sy-datum && '-' && sy-uzeit.
    ls_objectdescr-language     = 'E'.
    ls_objectdescr-language_iso = 'EN'.
    APPEND ls_objectdescr TO lt_objectdescr.

    " CREATE ATTACHMENT
    CALL FUNCTION 'BAPI_DOCUMENT_CREATE2'
      EXPORTING
        documentdata         = ls_docdata
      IMPORTING
        documenttype         = lv_doctype
        documentnumber       = lv_docnumber
        documentpart         = lv_docpart
        documentversion      = lv_docversion
        return               = ls_return
      TABLES
        documentdescriptions = lt_objectdescr.


      COMMIT WORK AND WAIT.


* Files Data
    ls_files-filename    = ls_entity-file_name.
    ls_files-storage_cat = ‘Mention Storage Category’.
    ls_files-appnr       = '1'.
    ls_files-dappl       = ls_entity-file_application.    "PDF/WIN
    ls_files-description = ls_entity-file_description.
    ls_files-checked_in  = 'X'.
    ls_files-updateflag  = 'I'.
    APPEND ls_files TO lt_files.

* Control Data
    ls_api_control-no_update_task = 'X'.
    ls_api_control-api_mode       = 'X'.
    ls_api_control-commit_flag    = 'X'.

* Prepare checkin file data.
    LOOP AT lt_content INTO DATA(lw_content).
      ls_pdf_checkin-dokar = lv_doctype.
      ls_pdf_checkin-doknr = lv_docnumber.
      ls_pdf_checkin-dokvr = lv_docversion.
      ls_pdf_checkin-doktl = lv_docpart.
      ls_pdf_checkin-appnr = '1'.
      ls_pdf_checkin-zaehl = sy-tabix.
      ls_pdf_checkin-orln  = lv_op_length.
      ls_pdf_checkin-orblk = lw_content.
      APPEND ls_pdf_checkin TO lt_pdf_checkin.
      CLEAR : ls_pdf_checkin.
    ENDLOOP.

* Wait for 10 seconds
    WAIT UP TO 5 SECONDS.

    CALL FUNCTION 'CVAPI_DOC_CHECKIN'
      EXPORTING
        pf_dokar           = lv_doctype
        pf_doknr           = lv_docnumber
        pf_dokvr           = lv_docversion
        pf_doktl           = lv_docpart
        pf_ftp_dest        = 'SAPFTPA'
        pf_http_dest       = 'SAPHTTPA'
        ps_api_control     = ls_api_control
      IMPORTING
        psx_message        = ls_msg
      TABLES
        pt_files_x         = lt_files
        pt_content         = lt_pdf_checkin.

      COMMIT WORK AND WAIT.


Consume ODATA from Power Automate

Before we did into Power Automate, it is important to mention that we tested the endpoint using Postman and it worked really well – however while automating it using Power Automate, it failed with the following error “CSRF Token Validation Failed”.

I found from this blog that we had to pass the Cookie – from the initial POST while getting the x-csrf token – to the next POST while posting the document; that too in some specific format.

We are going to see all these in detail.

This is how the entire flow looks alike


1. Trigger the flow on a Selected File from SharePoint Online


2. Get file properties


3. Get file content


4. HTTP – GET x-csrf-token

in ODATA, on change requests (PUT, POST, and DELETE) of REST clients to an ABAP server, the client has to provide a CSRF (Cross-Site Request Forgery) token.

Such a token can be retrieved via a previous service call to the ABAP server. For this, first on a none-changing call (GET, HEAD, OPTIONS), the client has to get this token by setting the HTTP header X-CSRF-Token to the value Fetch. A CSRF token is returned by the ABAP server in the same header and can be used for subsequent, server state changing calls using header X-CSRF-Token.

If this header is not present on a server state changing REST call, the server will respond with a HTTP 403 (“Forbidden”) return code, the HTTP header is set to “Required” and an error text (for example, “CSRF token validation failed”) is returned.

The validity of the CSRF token depends on the release of the ABAP component SAP_BASIS and on the activation of the security session management (which is controlled via the transaction SICF_SESSIONS on the granularity of SAP clients):

a) Release < 7.03/7.31 or the security session management is inactive: An own CSRF cookie gets generated (sap-XSRF_<SystemID>_<SAPClient>) and this CSRF token remains valid for 24 hours (86400 seconds).

b) Release >= 7.03/7.31, the validity is bound to the security session, which depends on the system parameter http/security_session_timeout value (see transaction RZ11 for details on this parameter). By default, the security session management is active in these releases.


5. Compose – Base64

base64(outputs(‘Get_file_content’)?[‘body’])


6. Initialize variable – FileName


7. Variable – Set x-csrf-token


8. Variable – Set-Cookie


9. Various compose actions are needed to set the cookie

Compose – Split All
split(variables(‘varSet-Cookie’),’;’)

Compose – Sap-client
split(variables(‘varSet-Cookie’),’;’)[0]

Compose – MYSAPSSO2
split(split(variables(‘varSet-Cookie’),’;’)[1],’,’)[1]

Compose – SAP SESSIONID
split(split(variables(‘varSet-Cookie’),’;’)[4],’,’)[1]

Compose – SPNegoTokenRequested
formatDateTime(utcNow(),’yyyy-MM-dd HH:mm:ss’)

Compose – Postman Cookie Format
concat(outputs(‘Compose_-MYSAPSSO2′),’; ‘,outputs(‘Compose-SAP_SESSIONID’),’; ‘,outputs(‘Compose-SPNegoTokenRequested’),’; ‘,outputs(‘Compose-_Sap-client’))

Compose – Document Extension
split(variables(‘varFileName’),’.’)[1]


10. Check whether the uploaded file is a PDF or an OFFICE File


11. Finally HTTP – POST to SAP Easy DMS


This was an interesting approach to connecting SAP using ODATA from a Microsoft Power Platform.

Hoping to share my knowledge soon.

Thanks for reading this post 🙂

Power Apps – Express Design (Build an app in seconds) – Figma to app

figma to app

In continuation with our first series – Power Apps – Express Design (Build an app in seconds) – Image to app, let us see how Figma app works.

Figma is a vector graphics editor and prototyping tool which is primarily web-based, with additional offline features enabled by desktop applications for macOS and Windows. The Figma mobile app for Android and iOS allows viewing and interacting with Figma prototypes on real-time mobile devices.

Figma to App bridges that gap between design and development, with Figma to App designers and developers can collaborate together to build an optimal experience for the end-users.

As a designer, you will simply create your design using Figma and then you upload that Figma file to Power Apps which will be taking care of converting your design into a working app.

Without further ado, let’s jump into the action.

Design in Figma

1. Go to the www.figma.com and create an account, then on the left navigation menu click on Community and look for a template named NETFLIX (first prototype)

NETFLIX

2. Click on Duplicate to load in the Designer tool – this is the place for all the customizations


3. Copy the URL from the URL Bar (see red circle in step 2 image).


4. Create a Figma personal access token using the following steps

  • On the Figma home page, click settings
  • Look for Personal access tokens, add a token description and create a new one
  • An important point to note is to copy the token
Figma settings
Personal access tokens

5. Go to Power Apps and create a Figma (preview) app

Figma (preview) app

And voila, once more – Power Apps has provisioned from Figma in a few minutes

Netflix by Power Apps

This is a huge productivity (design) saver that will allow businesses to roll out user-friendly, great-looking apps to their users in a short amount of time and effort.


API to app

At the time of writing this blog, the App-from-API feature is to be released in early July 2022.

Coming soon…

Power Apps – Express Design (Build an app in seconds) – Image to app

turn images and designs into apps using AI-powered express design

The need for Digitization is constantly growing and there are never enough resources (Cost-Scope-Schedule) to fulfil all the requirements, therefore Microsoft on 25th May during the recent Microsoft Build event, has introduced the “Express Design – Build an app in seconds” which is a new Power Apps features that accelerate the process for getting started by taking existing content (e.g.: a picture of your paper form, a screenshot of a design, a PPT, a PDF or a Figma design file) and converting them in working Power App with UI and data without requiring the maker to learn how to build an app.

This magic is done using Azure Cognitive Vision OCR model to recognize the text from your image as well as the Azure Computer Vision Object Detection model to recognize the controls on the image whether it’s a text input, a label or radio button, etc.

Azure Cognitive Vision OCR & Azure Computer Vision Object Detection model

After that, even though it’s optional, however, it’s recommended for you to set up the data through dataverse, so you will have your data stored in dataverse.

We have got three different options and we are going to see each one of them

  • Image to app
  • Figma to app
  • API to app

Image to app

Image to app

Let’s get started by building an Image to app

1. On Power Apps, click Create and select Image (preview)

Image (preview)

2. The Upload an image screen appears, where either you upload an image of your own or start with some sample images – in our case, let us upload the following Car Details Application Wireframe

Upload an image

Car Details Wireframe

3. After Azure identifies the component, tag and assign each component as per the requirements

Assign components

4. Next the system allows you to create a new table in Dataverse (recommended), or simply skip it for now.


5. In this step, map the column as per the required data type, review then create.

Columns mapping

And voila, in a few minutes – Power Apps has provisioned the app as per the given input!!!

Car details application

This is a whole new world of possibilities for the citizen developers, those architects or building technicians who are looking for a genuine alternative to building an app – truly it is Empowering every person and every business on the planet to achieve more.


Now, we are going to see how Figma to app works.

Team Collaboration on PowerApps

Introduction

The much-awaited feature has finally come in Power Apps which allows a team to work simultaneously in one Power Apps application.

Forget now locked for editing message now – this sounds exciting, isn’t 😊

locked for editing

Together, we are going to create a sample app in Power Apps and enable the collaboration with Azure DevOps.

Without further ado, let’s get started with step-by-step instructions.

1. Create a Help Desk application

Let’s assume our organization is in need of a Help Desk application.

To make our life easier Microsoft has come with a plethora of templates ready to be provisioned, so let us build the application.

  • In Power Apps left navigation Menu, click on + Create
  • Locate Start from template, and look for Help Desk
  • Provide a meaningful name and click create
  • and voila, in less than a minute our app is ready to use
Help Desk

Now let’s say our Help Desk Admin needs to have a customized report for the respective technology stream in the organization – for example SAP Report, Microsoft Dynamics Report, etc – (please note that we will not be implementing any report in this blog, this would be out of scope).

To make it simpler, we will have 2 screens so each developer can work simultaneously on the same application.

Power Apps Developer Screen

2. Connect with Azure DevOps

2.1 What is DevOps?

It is a compound of development (Dev) and operations (Ops), DevOps is the union of people, processes and technology to continually provide value to customers.

2.2 What does DevOps mean for teams?

DevOps enables formerly siloed roles—development, IT operations, quality engineering and security—to coordinate and collaborate to produce better, more reliable products. By adopting a DevOps culture along with DevOps practices and tools, teams gain the ability to better respond to customer needs, increase confidence in the applications they build and achieve business goals faster.

2.3 The benefits of DevOps

Teams that adopt DevOps culture, practices and tools become high-performing, building better products faster for greater customer satisfaction. This improved collaboration and productivity are also integral to achieving business goals like these:

The benefits of DevOps

2.4 Login or Sign Up For A Free Azure DevOps Account

Login to dev.azure.com or sign up for a free account, we need it to setup a repository which we are going to see in the next steps.

Once logged in, create a New DevOps Project.

Create a New DevOps Project

Provide a Project Name.

Create a New DevOps Project

Click on Repos, then Initialize the repository.

Initialize the repository

2.5 Keep a note of the Azure DevOps Git Repository URL and Branch

It should be as follows:

[https://dev.azure.com/organization_name/project_name/_git/repo_name]

for our case, it would be:

[https://dev.azure.com/organization_name/Help%20Desk/_git/Help%20Desk]

the branch name would be:

[main]

2.6 Create a DevOps Project Personal Access Token

A personal access token (PAT) as an alternate password to authenticate into Azure DevOps, it contains your security credentials for Azure DevOps. A PAT identifies you, your accessible organizations, and scopes of access. As such, they’re as critical as passwords, so you should treat them the same way.

In the DevOps Project, under the User Settings (top-right corner), click on Personal access tokens

Personal access tokens

Create a new personal access token

Personal access tokens

Make sure you copy the token and keep it securely

Personal access tokens

3. Connect Azure DevOps Git with Power Apps

It is time now to connect our application with the repository:

3.1 Enable the Git version control setting

In Power Apps, Go to File > Settings > Upcoming features and enable the Git version control setting:

Git version control setting

Once connected, click on the Git version control then connect.

Enter the value previously taken from the above step 2.5

Git version control setting

3.2 Sign in to your DevOps repository

Sign in to your DevOps repository using your DevOps username and your Personal Access Token – see step 2.6

Sign in to your DevOps repository

It creates a Directory for you if not found.

It creates a Directory for you if not found.

4 Collaborate with your team

4.1 Provide User Access or Grant User Access in the Organization Settings

We need to explicitly add the users who need to access the Organization/Projects. This is an extra security layer which is necessary for the Organization Administrator to perform.

4.2 Share the Help Desk app with your developer

Once the given user(s) have been provided access, now it is time now to share and collaborate, for that we need to share the app as co-owner

Share the Help Desk app with your developer

4.3 Login simultaneously to the app

Important note 1: we recommend closing and re-opening the main browser as it may result in an unwanted experience.

Important note 2: The second developer must login using his Active Directory Username and for the password use the Personal Token Access – see step 2.6


and voila, the second developer sees the same application and its artifacts.

Login simultaneously to the app

5 Commit changes and check for Git updates

Let’s say that the second developer (right screen) has to work on a Jira Report, therefore he adds the screen

Login simultaneously to the app

As his work is completed, he needs to commit so other developers can see his changes – this is done using the Sync button.

Commit changes and check for Git updates

Once committed, the other developers use the same button to sync the application and here is how it looks:

Login simultaneously to the app


5. Summary

This is a powerful and much-needed feature to deploy applications more quickly and seamlessly.

Additionally, Azure DevOps provide improved collaboration and productivity which lead to building better products faster for greater customer satisfaction.

Furthermore, this process allows the team to have complete code control such as code review, editing the code itself in a different application such as Visual Studio Code Editor, etc

Lastly, if you find this article useful, please share it with your friends. Every little bit helps. Thank you!

Update User Profile Picture across all Office 365 apps and Skype for Business using Power Apps, SharePoint, Graph Api & Azure Web Jobs – Part 2

There was a requirement that any user in the organization can update his/her profile picture across all Office 365 apps – the approach was quite straight forward: use Power Apps, save the user data in SharePoint and use Power Automate HTTP connector to do a POST using a Graph API endpoint – however, it seems now that the Power Automate HTTP is a Premium connector which becomes overpriced as all the users in the organization are going to use it, therefore used Azure Web Jobs which did the job well. Note that WebJobs provide an easy way to run scripts or programs as background processes in the context of your app.

Let’s get started.

This is the continuation of Part 1 (Power Apps & SharePoint)

B. Azure Web Jobs & Grap API

B1. Create an Azure Web App to host your code.

– Under Project Details, select Subscription and Resource Group.

– Under Instance Details, provide a descriptive Name, and set as follows – Publish: Code, Runtime stack: latest .Net version, Operating System: Window.

B2. Download the publish profile

Once the Web App is provisionned, Get the publish profile from the Overview menu.

B3. Create a Console Application using Visual Studio 2017 or later.

Once created, right-click on the Project and click Publish as Azure WebJob… to create the Web Job within Visual Studio.

– Provide a descriptive WebJob name.

– Select a WebJob run mode: Run Continuously or Run on Demand. (There is a Scheduled run option as well which we will see in the section B5).

B4. On the next Publish screen, Import the profile settings which you saved in steps B2.

B5. Change the Webjob run mode to Scheduled.

Open the webjob-publish-settings.json within Properties and change the code as follows:

Please note that Scheduled WebJob will be executed based on provided CRON expression. Click here to learn more about CRON Expression.

{
  "$schema": "http://schemastore.org/schemas/json/webjob-publish-settings.json",
  "webJobName": "WebJobUpdateProfilePicture",
  "runMode": "Scheduled",
  "schedule": "0 */1 * * * *"
}

B6. Replace Program class with the below code.

– The Program class filters all the SharePoint items which are not yet updated, based on the IsUpdated column.

– Thereafter, ProcessUpdateUserPicture class contains all Graph API (C#) related functions to updated the image to Office 365.

public class Program
    {
        #region Variables
        static StringBuilder logMessage;
        static string serviceAccount = "user@myorg.com";
        static string serviceAccountPWD = "myPassword";
        static string listName = "UsersProfileData";
        static string COLUMN_IS_UPDATED = "IsUpdated";
        static string COLUMN_EMPLOYEE_AS_TEXT = "EmployeePhotoAsText";
        static string COLUMN_TITLE = "Title";
        static string COLUMN_EMPLOYEE_UPN = "EmployeeUPN";
        #endregion

        /// <summary>
        /// Main Mthod
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Web Job started");
                ProcessProfileUpdation("url_of_your_sharepointsite");
                Console.WriteLine("Web Job Completed");
            }
            catch (Exception ex)
            {
                logMessage.AppendLine(string.Format("Exception occured in Main - {0}", ex.ToString()));
            }
            finally
            {
            }

        }

        /// <summary>
        /// Use to process profile updation for all entered users photos
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string ProcessProfileUpdation(string url)
        {
            try
            {
                //Authenticate
                using (var ctx = new Microsoft.SharePoint.Client.ClientContext(url))
                {
                    var passWord = new SecureString();
                    foreach (char c in serviceAccountPWD.ToCharArray()) passWord.AppendChar(c);
                    ctx.Credentials = new SharePointOnlineCredentials(serviceAccount, passWord);

                    UpdateProfilePhoto(ctx);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("Exception occured in Main - {0}", ex.ToString()));
            }
            return string.Empty;
        }

        /// <summary>
        /// Method used to update profile photo
        /// </summary>
        /// <param name="ctx"></param>
        private static void UpdateProfilePhoto(ClientContext ctx)
        {
            try
            {
                //Get  List
                List oList = ctx.Web.Lists.GetByTitle(listName);

                //Filter the ones which are not yet Updated
                CamlQuery camlQuery = new CamlQuery();
                camlQuery.ViewXml = @"<View><Query><Where><Eq><FieldRef Name='IsUpdated' /><Value Type='Boolean'>0</Value></Eq></Where></Query></View>";

                ListItemCollection collListItem = oList.GetItems(camlQuery);

                ctx.Load(collListItem);
                ctx.ExecuteQuery();

                foreach (ListItem oListItem in collListItem)
                {
                    bool boolValue = Convert.ToBoolean(oListItem[COLUMN_IS_UPDATED]);

                    if (boolValue == false)
                    {
                        string employeePhotoAsText = oListItem[COLUMN_EMPLOYEE_AS_TEXT].ToString();
                        string employeeName = oListItem[COLUMN_TITLE].ToString();
                        string employeeUPN = oListItem[COLUMN_EMPLOYEE_UPN].ToString();

                        var base64Data = Regex.Match(employeePhotoAsText, @"data:image/(?<type>.+?),(?<data>.+)").Groups["data"].Value;
                        byte[] bytes = Convert.FromBase64String(base64Data);
                        System.IO.Stream imageStream = new MemoryStream(bytes);

                        Stream oldImageStream = new MemoryStream();
                        bool isUpdated = ProcessUpdateUserPicture.StartUpdation(ctx, employeeUPN, imageStream, out oldImageStream, logMessage);

                        Console.WriteLine(string.Format("ID: {0} \nEmployeeUPN: {1}", oListItem.Id, employeeUPN));
                        if (isUpdated == true)
                        {
                            oListItem["IsUpdated"] = true;
                            oListItem.Update();
                            ctx.ExecuteQuery();
                        }
                        oListItem.Update();
                        ctx.ExecuteQuery();
                    }
                }
            }
            catch (Exception ex)
            {
                logMessage.AppendLine("Exception occured at UpdateProfilePhoto: - " + ex.ToString());
                Console.WriteLine("Exception occured at UpdateProfilePhoto: - " + ex.ToString());
            }
        }
    }

public class ProcessUpdateUserPicture
    {
        public static string accessToken = null;

        /// <summary>
        /// Use to update profile photo for each user
        /// </summary>
        /// <param name="clientContext">clientContext</param>
        /// <param name="userId">employeeUPN</param>
        /// <param name="streamImage">IMage stream</param>
        /// <param name="logMessage">logMessage</param>
        /// <returns></returns>
        public static bool StartUpdation(ClientContext clientContext, string userId, Stream streamImage, out Stream previousPhoto, StringBuilder logMessage)
        {
            bool isUpdated = false;
            previousPhoto = new MemoryStream();
            try
            {
                logMessage.AppendLine("Calling GetAuth()");
                try
                {
                    GraphServiceClient graphService = GetAuth(clientContext, logMessage);
                    logMessage.AppendLine("After Calling GetAuth()");
                    var result = graphService.Users[userId].Photo.Content.Request().PutAsync(streamImage); //users/{1}/photo/$value
                    do
                    {
                        logMessage.AppendLine(string.Format("Result status: {0}", result.Status));
                        Console.WriteLine("Result status: {0}", result.Status);
                        Thread.Sleep(20000);
                    } while (result.Status == System.Threading.Tasks.TaskStatus.WaitingForActivation);
                    if (result.IsCompleted == true)
                    {
                        if (result.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
                        {
                            isUpdated = true;
                            Console.WriteLine(string.Format("Profile updated for - {0} successfully", userId));
                            logMessage.AppendLine(string.Format("Profile updated for - {0} successfully", userId));

                        }
                        else
                        {
                            logMessage.AppendLine(string.Format("Profile process failed for - {0} \nException - {0}", userId, result.Exception.InnerException.Message));
                            Console.WriteLine(string.Format("Profile process failed for - {0} \nException - {0}", userId, result.Exception.InnerException.Message));
                        }
                    }
                }
                catch (Microsoft.Graph.ServiceException svcEx)
                {

                    var additionalData = svcEx.Error.AdditionalData;
                    logMessage.AppendLine(string.Format("Microsoft.Graph.ServiceException svcEx - {0}", additionalData["details"].ToString()));
                }

            }
            catch (Exception ex)
            {
                logMessage.AppendLine(ex.ToString());
                Console.WriteLine(ex.ToString());
            }

            return isUpdated;
        }

       /// <summary>
        /// Access token for Graph API call
        /// </summary>
        /// <param name="logMessage"></param>
        /// <returns></returns>
        private static string GetAccessToken(StringBuilder logMessage)
        {
            try
            {
                Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(Globals.AuthorityUrl, true);
                Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential creds = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(Globals.ClentId, Globals.ClientSecret);
                Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult authenticationResult = authContext.AcquireTokenAsync(Globals.GraphResourceUrl, creds).Result;
                return authenticationResult.AccessToken;

            }
            catch (Exception ex)
            {
                logMessage.AppendLine(string.Format("Exception occured at GetAccessToken - {0}", ex.ToString()));
            }

            return null;
        }

        /// <summary>
        /// Method used for Graph API call
        /// </summary>
        /// <param name="clientContext"></param>
        /// <param name="logMessage"></param>
        /// <returns></returns>
        private static GraphServiceClient GetAuth(ClientContext clientContext, StringBuilder logMessage)
        {
            try
            {
                accessToken = GetAccessToken(logMessage);
                GraphServiceClient graphClient = GetGraphClient(clientContext, accessToken, logMessage);
                return graphClient;


            }
            catch (Exception ex)

            {
                logMessage.AppendLine(string.Format("Exception occured at GetAuth - {0}", ex.ToString()));
                //CustomLogs.LogError(clientContext, string.Format("Exception from GetAuth"), ex);
            }
            return null;
        }

        /// <summary>
        /// Method used for Graph API call
        /// </summary>
        /// <param name="clientContext"></param>
        /// <param name="graphToken"></param>
        /// <param name="logMessage"></param>
        /// <returns></returns>
        public static GraphServiceClient GetGraphClient(ClientContext clientContext, string graphToken, StringBuilder logMessage)
        {
            try
            {
                DelegateAuthenticationProvider authenticationProvider = new DelegateAuthenticationProvider(
                (requestMessage) =>
                {
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", graphToken);
                    return Task.FromResult(0);
                });
                return new GraphServiceClient(authenticationProvider);
            }
            catch (Exception ex)
            {
                logMessage.AppendLine(string.Format("Exception from GetGraphClient - {0} ", ex.ToString()));
            }
            return null;
        }

    }

B7. Deploy the code to the Azure Web App.

In Visual Studio, right-click on the Project, choose to Publish as Azure WebJob then click on Publish to deploy the code.

Once successfully deployed, in the Azure Web App, the job entry must be present within WebJobs – the job is run to scheduled every 15mn in this case.

To ensure the job has run successfuly, click on Logs to see more details.

Cheers!

Update User Profile Picture across all Office 365 apps and Skype for Business using Power Apps, SharePoint, Graph Api & Azure Web Jobs – Part 1

There was a requirement that any user in the organization can update his/her profile picture across all Office 365 apps – the approach was quite straight forward: use Power Apps, save the user data in SharePoint and use Power Automate HTTP connector to do a POST using a Graph API endpoint – however, it seems now that the Power Automate HTTP is a Premium connector which becomes overpriced as all the users in the organization are going to use it, therefore used Azure Web Jobs which did the job well. Note that WebJobs provide an easy way to run scripts or programs as background processes in the context of your app.

Let’s get started.

A. Power Apps & SharePoint

A1. Create a new blank app with a Tablet layout preferably.

A2. Insert an Add picture control to upload the image.

Set the OnSelect property as follows

Set(CapturedPic, UploadedImage1.Image);
Set(vImg,JSON(UploadedImage1.Image,JSONFormat.IncludeBinaryData));
If(Value(Text(Len(vImg) * .00000073,”[$-en-US]##.##”)) >= 4, Notify(“Please choose an image less than 4 Mb”),””);

A3. Insert an Image control to validate the uploaded image.

In the Image property, set it as CapturedPic

A4. Add some labels to make the app more descriptive as follows:

It is preferable to show the image size in a label, the reason being is that Graph API support only an image size less than 4Mb, to show the size set the Text as: “Image size: ” & Text(Len(vImg)*.00000073,”[$-en-US]##.##”) & ” Mb”

A5. Now comes the submission of data to SharePoint

On the OnSelect button, add the following code

//Used for delegation purpose
ClearCollect(
    userImage,
    imgCapture.Image
);

//Checking whether the entry of the same user exists in the list
ClearCollect(
    IsEntryExists,
    Filter(
        UsersProfileData,
        EmployeeUPN = CurrentUser.Email
    )
);

//If so then Update otherwise Add
If(
    CountRows(IsEntryExists) > 0,
    Patch(
        UsersProfileData,
        LookUp(
            UsersProfileData,
            EmployeeUPN = CurrentUser.Email
        ),
        {
            Title: User().FullName,
            EmployeeDisplayName: CurrentUser.FullName,
            EmployeeUPN: CurrentUser.Email,
            EmployeeMail: Office365Users.MyProfile().Mail,
            EmployeePhotoApproval: If(
                chkApprove.Value = true,
                "yes",
                "no"
            ),
            IsUpdated: false,
            UserLanguage:Lower(Language()),
            EmployeePhotoAsBase64: First(userImage).Url,
            EmployeePhotoAsText: Substitute(
                JSON(
                    imgCapture.Image,
                    JSONFormat.IncludeBinaryData
                ),
                """",
                ""
            )
        }
    ),
    Patch(
        UsersProfileData,
        Defaults(UsersProfileData),
        {
            Title: User().FullName,
            EmployeeDisplayName: CurrentUser.FullName,
            EmployeeUPN: CurrentUser.Email,
            EmployeeMail: Office365Users.MyProfile().Mail,
            EmployeePhotoApproval: If(
                chkApprove.Value = true,
                "yes",
                "no"
            ),
            IsUpdated: false,
            IsDeleted: false,
            UserLanguage:Lower(Language()),
            EmployeePhotoAsBase64: First(userImage).Url,
            EmployeePhotoAsText: Substitute(
                JSON(
                    imgCapture.Image,
                    JSONFormat.IncludeBinaryData
                ),
                """",
                ""
            )
        }
    )
);

//Reset all controls and notify
Set(
    CapturedPic,
    Blank()
);
Reset(chkApprove);
Reset(AddMediaButton1);
Notify("Photo submitted successfully - please check after sometimes in Delve portal.");

This is the structure of the SharePoint list

The application should like as follows

Next: Part 2 – Azure Web Jobs & Grap API