Guest Access with Field Service Mobile: Introducing Tenant Switcher for Field Service Mobile

Guest Access with Field Service Mobile: Introducing Tenant Switcher for Field Service Mobile

This article is contributed. See the original author and article here.

A common scenario for Field Service organizations is to augment their staff with external vendor resources.  Leveraging Azure Active Directory B2B Guest Access, vendors can be added to the organizational directory without being created as full first party users within the organization. This allows a clean delineation of users to manage security and data access. 

Dynamics 365 has made this vendor onboarding process even easier with Wave 1 2023 by introducing Tenant Switcher for Field Service Mobile. Tenant Switcher provides a user interface where guest users can now easily switch between their Home and Guest Tenants. 

Other considerations to note: 

  • Guest Users require a Field Service license and appropriate Security role for access to Field Service Mobile. 
  • Model Driven Application Authentication supports work or school accounts. AAD B2B Guest users configured with a personal account would not be able to authenticate and access the Field Service Mobile application directly. 

Field Service (Dynamics 365) mobile app overview  – Dynamics 365 Field Service | Microsoft Learn

The post Guest Access with Field Service Mobile: Introducing Tenant Switcher for Field Service Mobile appeared first on Microsoft Dynamics 365 Blog.

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.

Error reference article now available in Microsoft Learn

This article is contributed. See the original author and article here.

The Viva Insights team recently published a new article on Microsoft Learn: Error messages in Viva Insights | Microsoft Learn


 


If you’re seeing a message related to one of these issues, check out our new document:



  • Page access

  • License assignment

  • Data upload

  • Organization insights


As always, if this article doesn’t have the answers you’re looking for, our support team is happy to help.

How Microsoft Teams helped the Breakthru app bring wellbeing to 45,000 organizations

How Microsoft Teams helped the Breakthru app bring wellbeing to 45,000 organizations

This article is contributed. See the original author and article here.

The Breakthru app in Teams is available to more than 300 million potential monthly active users in 500,000 organizations. Finding the right audience is critical for independent software vendors (ISVs), and just three years after launching on Teams, Breakthru reaches more than 45,000 organizations worldwide, with a growing customer base.

The post How Microsoft Teams helped the Breakthru app bring wellbeing to 45,000 organizations appeared first on Microsoft 365 Blog.

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.

Lesson Learned #368: Connection Retry-Logic using ODBC API code

This article is contributed. See the original author and article here.

This week I had a service request where our customer didn’t have a connection retry logic implemented in their application code in the event of a connection failure to Azure SQL. I would like to share an example about how to implement it. 


 


First the C# code using ODBC API:


 


 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace DotNetExample
{
    class ClsODBCAPI
    {
        // Import the ODBC API functions using P/Invoke
        [DllImport("odbc32.dll")]
        private static extern short SQLAllocHandle(short handleType, IntPtr inputHandle, out IntPtr outputHandle);

        [DllImport("odbc32.dll")]
        private static extern short SQLSetEnvAttr(IntPtr environmentHandle, int attribute, IntPtr valuePtr, int stringLength);

        [DllImport("odbc32.dll")]
        private static extern short SQLConnect(IntPtr connectionHandle, string serverName, short nameLength1, string userName, short nameLength2, string password, short nameLength3);

        [DllImport("odbc32.dll")]
        private static extern short SQLExecDirect(IntPtr statementHandle, string query, int textLength);

        [DllImport("odbc32.dll")]
        private static extern short SQLFetch(IntPtr statementHandle);

        [DllImport("odbc32.dll")]
        private static extern short SQLGetData(IntPtr statementHandle, short columnIndex, short targetType, IntPtr targetValue, int bufferLength, out int indicatorValue);

        [DllImport("odbc32.dll")]
        private static extern short SQLDisconnect(IntPtr connectionHandle);

        [DllImport("odbc32.dll")]
        private static extern short SQLFreeHandle(short handleType, IntPtr handle);

        [DllImport("odbc32.dll")]
        private static extern short SQLGetDiagRec(
            short handleType,
            IntPtr handle,
            short recordNumber,
            IntPtr sqlState,
            out int nativeError,
            IntPtr messageText,
            short bufferLength,
            out short textLength
        );

        public void Main()
        {
            // Initialize ODBC environment handle
            IntPtr environmentHandle = IntPtr.Zero;
            SQLAllocHandle(1, IntPtr.Zero, out environmentHandle);
            SQLSetEnvAttr(environmentHandle, 200, (IntPtr)3, 0);

            // Initialize ODBC connection and statement handles
            IntPtr connectionHandle = IntPtr.Zero;
            IntPtr statementHandle = IntPtr.Zero;
            short retcode;
            retcode = SQLAllocHandle(2, environmentHandle, out connectionHandle);

            try
            {
                // Connect to the database
                retcode = RetryLogicUsingODBCAPI(connectionHandle);

                if( retcode != 1  )
                    {
                        return;
                    }

                retcode = SQLAllocHandle(3, connectionHandle, out statementHandle);
                // Prepare and execute a query
                SQLExecDirect(statementHandle, "SELECT top 200 TextToSearch FROM PerformanceVarcharNVarchar", 60);

                // Fetch and display the result set
                int id = 0;
                while (SQLFetch(statementHandle) == 0)
                {
                    // Retrieve data for each column
                    id = id + 1;
                    int nameLength = 200;
                    IntPtr namePtr = Marshal.AllocHGlobal(nameLength);
                    SQLGetData(statementHandle, 1, 1, namePtr, nameLength, out nameLength);
                    string name = Marshal.PtrToStringAnsi(namePtr);

                    Console.WriteLine("ID: " + id);
                    Console.WriteLine("Name: " + name);

                    Marshal.FreeHGlobal(namePtr);
                }
            }
            catch (Exception ex)
            {
                // Handle any errors that occur
                Console.WriteLine("Error: " + ex.Message);
            }
            finally
            {
                // Disconnect and free resources
                SQLDisconnect(connectionHandle);
                SQLFreeHandle(3, statementHandle);
                SQLFreeHandle(2, connectionHandle);
                SQLFreeHandle(1, environmentHandle);
            }

        }

        private short RetryLogicUsingODBCAPI(IntPtr connectionHandle)
        {
            int maxRetryAttempts = 5;
            int retryIntervalSeconds = 10;
            int retryCount = 0;
            short retcode = 0;

            TimeSpan ts;
            string elapsedTime;

            Stopwatch oConnTime = new Stopwatch();

            while (retryCount < maxRetryAttempts)
            {
                try
                {
                    retryCount++;
                    retcode = SQLConnect(connectionHandle, "DSNName", 7, "username", 8, "Password", 8);

                    if (retcode == 1)
                    {
                        ts = oConnTime.Elapsed;
                        elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
                        Console.WriteLine("Connected to the database. Time Spent:" + elapsedTime);
                        return retcode;
                    }
                    else
                    {
                        Console.WriteLine("SQLConnect failed with retcode: " + retcode);
                        GetODBCErrorDetails(connectionHandle);
                        Console.WriteLine("Retrying connection...in...{0} ms", (1000 * retryIntervalSeconds));
                        System.Threading.Thread.Sleep(1000 * retryIntervalSeconds);
                        retryIntervalSeconds = Convert.ToInt32(retryIntervalSeconds * 1.5);
                    }

                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error: " + ex.Message);
                }
            }
            return -1;
        }

        static void GetODBCErrorDetails(IntPtr handle)
        {
            const int SQL_HANDLE_ENV = 1;
            const int SQL_HANDLE_DBC = 2;

            IntPtr sqlStatePtr = Marshal.AllocHGlobal(6);
            IntPtr messageTextPtr = Marshal.AllocHGlobal(1024);
            int nativeError;
            short textLength;

            short retcode = SQLGetDiagRec(
                SQL_HANDLE_DBC,
                handle,
                1,
                sqlStatePtr,
                out nativeError,
                messageTextPtr,
                1024,
                out textLength
            );

            if (retcode == 0)
            {
                string sqlState = Marshal.PtrToStringAnsi(sqlStatePtr);
                string messageText = Marshal.PtrToStringAnsi(messageTextPtr, textLength);
                Console.WriteLine("ODBC Error Details:");
                Console.WriteLine("SQLState: " + sqlState);
                Console.WriteLine("Native Error: " + nativeError);
                Console.WriteLine("Message: " + messageText);
            }
            else
            {
                Console.WriteLine("Failed to retrieve ODBC error details.");
            }

            Marshal.FreeHGlobal(sqlStatePtr);
            Marshal.FreeHGlobal(messageTextPtr);
        }
    }

}

 


 


 


This first of the code declares and imports the required functions from the odbc32.dll library using P/Invoke. These functions are used to interact with the ODBC API.


 


In the Main method, the ODBC environment handle is initialized using SQLAllocHandle function. The SQLSetEnvAttr function is used to set the environment attribute. Then, the ODBC connection and statement handles are initialized using SQLAllocHandle.


 


Inside the try block, the RetryLogicUsingODBCAPI method is called to establish a connection to the database. If the connection is successful (retcode = 1), a query is executed using SQLExecDirect. The result set is fetched using SQLFetch, and the data is displayed.


In case of any errors, the catch block handles and displays the exception message. The finally block is used to disconnect from the database and free the allocated resources.


 


The RetryLogicUsingODBCAPI method is responsible for implementing the connection retry logic. It attempts to connect to the database using SQLConnect within a while loop. If the connection is successful (retcode = 1), it returns the retcode. Otherwise, it displays the failure details, waits for a specified interval, and increases the interval for subsequent retries.


 


The GetODBCErrorDetails method retrieves ODBC error details using SQLGetDiagRec function. It takes the handle as input and retrieves the SQLState, native error code, and message text associated with the error.


 


Enjoy!

GitHub Copilot Fundamentals Learning Path – Your New AI programming friend!

GitHub Copilot Fundamentals Learning Path – Your New AI programming friend!

This article is contributed. See the original author and article here.

Hi!


Ready to meet your new best friend? Say hello to GitHub Copilot, the AI pair programmer that’s about to change the way you code. It’s like having a super-smart friend who’s always ready to help. No matter the scenario, writing code, fixing bugs, or just trying to remember that one command you always forget.


 


We’ve got a brand-new GitHub Copilot Fundamentals Learning Path all about GitHub Copilot. What’s a Learning Path, you may ask? Well, it’s a sequence of courses that guides you step-by-step to learn new skills and discover the power of Microsoft products. You can find all sorts of Learning Paths on Microsoft Learn.


 


Copilot in MS Learn.png


 

Our new Learning Path is split into two parts: “Introduction to GitHub Copilot” and “Introduction to GitHub Copilot for Business“.


In the first part, you’ll get to know GitHub Copilot and all its cool features. It’s like having a ChatGPT friend right in your editor, helping you out with code, error messages, and even generating unit tests. Plus, it’s got your back when you’re working on pull requests or need help with documentation. And let’s not forget about the command line – GitHub Copilot CLI is like the ultimate cheat sheet!


 

The second part is all about GitHub Copilot for Business. (Spoiler: this is where things get serious). We’re going to review business scenarios like: AI-based security vulnerability filtering, VPN proxy support, and a super simple sign-up process. Imagine having a complete squad of Coding experts ready to help your business code faster and smarter.


 

So, what are you waiting for? Explore our new GitHub Copilot Fundamentals Learning Path and start learning how to code with your new AI friend today!


 


Happy Coding!