Cuba, Socialism vs Capitalism from a good ole boy – Recap of my trip to Cuba!

It's interesting to me that my last two trips that have been out of the country have been to China and Cuba, two communistsocialist countries.  Being I'm a USA born, good ole boy from Oklahoma, we can just say that it was as eye opening as moving from Oklahoma to Seattle and getting "hit on" the first time by a gay guy, which isn't bad, just different!

My Background: 

A bit of my background…I grew up with almost nothing, worked hard and the wonderful U. S. of A provided and rewarded me based on that hard work.  My mom worked hard and paid for everything from her job and the little child support dad gave us.  From what I remember, food stamps kicked in at one time, so to say I never drank from the US Democratic faucet would be false.  So, what does that have to do with anything?  Cuba.  What an eye opening experience, even more so than China as they seem to be some kind of mix of socialism and capitalism (unlike the pure socialism of Cuba).  As you will see, its not practical for someone to be able to out grow your neighbors salary wise as the average salary in Cuba is right around $40, with developers making somewhere around $500/month.  But the reality is they don't have to pay for their education, or their health care.  Imagine that…you can take as many courses as you want, get as smart as you want…or be as lazy as you want.  If only we could do that in the USA…

Getting there:

Getting to Cuba is very easy.  You can buy a Visa online before you go, or at the point of departure (the actual flight to Cuba) you can buy it.  You also need health insurance, although, being that health care is free, it was a bit odd at this was required. The price of your ticket currently includes $25 health insurance policy and your boarding pass serves as your insurance card.  Be sure that you don't lose the second part of the Visa on your way back!

Cuban Government: 

Most things in China are owned by the government, the hotel we stayed in for instance, the Hotel National, is owned by the government:

 

Although a nice hotel, it is old and run down.  There are other hotels that offer much nicer rooms and amenities, but to have stayed in a hotel that some of the greats have stayed in…we will just say it was an experience you can't compare to anything else!

 

We walked down to the US Embassy which is only about half a mile from the hotel.  Its on the water on the Malcon and its a very nice looking building:

 

Prices and Money: 

There are two currencies in Cuba.  The CUC and the CUP.  The CUC is for tourists, the CUP is for the locals.  You can pay with either if you have it, you just are likely to have CUCs.  I never had an issue with someone paying change in CUP back, but you should be aware of it and this blog is very helpful.  Net net is CUC are pictures of monuments, CUP are pics of individuals.  The price to change out the US dollar was ok at the airport.  The hotel was actually better by about .01.  On the way out, I got back 99% of my USD from my CUC at the airport.

The prices in Cuba are soooo cheap!  Most drinks (Mojito anyone?) are $5CUC at major hotels, if you venture out, you will find them for $2-3CUC and yes, they are super strong wherever you go!

 

Food:

The food in Cuba was amazing!  There was not a single meal that I didn't like!  The prices were soo cheap that I actually would buy 2 meals each time (yeah, I may look skinny, but I eat a lot!). By the way, a massive super yummy dinner for 9 of us at a sit down nice restaurant, was $130CUC.   The one thing you need to realize about sites like TripAdvisor is that the reviews are made by tourists that come off of cruise ships and its a part of their excursion package.  So if you try to go to one of these top 10 places, you will very likely be turned away as they book the entire venue for the excursion folks.  Face control seems to work well, so we were lucky and actually got into some of the places by smiling and laughing a bit…once we the staff realized we weren't with the cruise ships, we got much better attention!

 

People:

The people in Cuba were sooo nice!  You can tell that not having to worry about getting an education, getting treated for a disease or sickness made them very carefree and easy going.  As an aside, they created a vaccine for lung cancer, it makes sense as one of their major exports is tobacco!  I was able to bring back some Cohiba Behikes (52/54/56).  If you don't know what those are, you gotta look em up!

 

InternetNetwork: 

Just know that your hotel wifi will likely not be the same wifi as the public uses and the username/password you get for the RADIUS server won't work out on the open.  I had 45 minutes left on the hotel wifi and could not log into the ETECSA wifi at the airport! 

Socialism: 

So…get this…my presentation was focused on "how to make money" in Cuba. You can find it here.  Needless to say, making money in Cuba as a corporation is not something you will be able to do for quite some time.  Locals are not allowed to start corporations, so as a programmer, you can't start a consulting company and hire people.  The concept of a corporation doesn't really exist in Cuba.  You have to get government approval for everything and as we painfully learned, you also need permits for tech gathering! 

SharePointMicrosoftBusiness:

The people in Cuba do have a computer science based curriculum that teaches C#.  They use Microsoft products.  They don't have the best computers to run things on.  Most computers are imported via family and friends.  So most places aren't going to have some fancy server room where you can run the latest and greatest server OS and server based products.  You also can't count on the network to support cloud services so don't even think about selling cloud services there.  You have to have a local presence.  Someone will need to build a co-lo facility to host all the major players.  I'm sure it will be owned by the Cuban government.  You'll just have to put your servers in it to get any decent bandwidth.  Google is trying harder than all the others, but the progress is slow.  There is a trial to doing broadband.  I did see co-axel cables run all over, and there were lots of CAT5 cables run between houses…a lot of the cables were used for door bells though…LOL.

Soccer: 

Kids are playing soccer everywhere!  It was very hard for me to not actually get out and play (I have a torn ACL right now), but I did manage to get the courage to brave a fully tore ACL in front of a cool church (yeah those are my old Gucci soccer shoes):

 

Random Photos:

McFly!  McFly!  Yeah, there are old cars everywhere and at times the smog was a bit unbearable, this guy seemed to make the best of it:

 

Cubans love their country!  Some cools pics:

 

Get your drugs at a Harry Potter drug store!  This place was deep inside Old Havana in what we would easily call a "run down" part:

 

Our trip home:

Getting home is easy, the airport is pretty fast, although I can see if there were a lot of people it might be a bit crowded at times.  But we had no issues.  The only issue we had was the flight path back home, evidentially we flew over a tornado:

 

Summary: 

Although the major business opportunities in Cuba are in the low teens, and "USCapitalism" like careers non-existent, the environment is fun and the people are amazingly smart and carefree.  They live with what they have and make the best of it.  Something that a majority of Americans need to learn to appreciate.  As I came home and walked through my front door, I felt that appreciation for what I have (and what I don't have and have had), and all the opprotunities I have been given by being "born in the USA".

But at the same time, I felt really sorry for myself in having the wool pulled over my eyes with the stigma and residual of capitalism and how things really shouldn't be based solely on money and success, but on what you can contribute to your community, country and family.  It's what you don't know you don't know…

Go to Cuba before it changes too much!
Chris 

Using Nintex Workflow Cloud and Azure Functions to call on-premises workflows

This is from a lab from the soon to be released Nintex End User Training courses.  Its the first of its kind and I'll simply say, you will learn some fun stuff!  

  1. Login to
    Azure, or create a trial for Azure Functions
    • Trial
      https://functions.azure.com/try
    • Portal
      1. Select
        your subscription
      2. For
        the function name, type “NintexExternalStart”
      3. Select
        a region
      4. Click
        “Create + get started”, this can take a few seconds
  2. Click the
    “New Function” button
  3. Select the
    “HttpTrigger-CSharp” template:
  4. Click
    “Create"
 Copy the following to your azure function:
 
#r "Newtonsoft.Json"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");    
    string url = "https://run.nintex.io/x-start/YourEndPoint";
    string securityKey = "YourSecurityKey";
    var variableValues = new Dictionary<string, object>();
    var service = new ExternalStartApiClient();
    try
    {
        log.Info("C# HTTP trigger function processed a request.");
        var correlationId = service.StartWorkflow(url, securityKey, variableValues, log);
    }
    catch
    {
    }
    string name = "Chris";
    return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
    
    public class ExternalStartApiClient
    {
        public RestClient Client { get; internal set; }
        #region Constructors
        public ExternalStartApiClient()
        {
            this.Client = new RestClient();
        }
        public ExternalStartApiClient(RestClient client)
        {
            this.Client = client;
        }
        #endregion
        /// <summary>
        /// Retrieves information about the workflow variables available for the Nintex workflow associated with the
        /// specified endpoint URL.
        /// </summary>
        /// <param name="endpointUrl">The External Start endpoint URL associated with a Nintex workflow.</param>
        /// <returns>A List object that contains a WorkflowVariable object for each available workflow variable.</returns>
        public List<WorkflowVariable> GetWorkflowVariables(string endpointUrl)
        {
            // Send a GET request to the specified endpoint URL. No authorization or authentication is
            // required for this request.
            HttpResponseMessage response = this.Client.Invoke(HttpMethod.Get, endpointUrl, null, null);
            // If a response is returned, check the HTTP status code.
            if (response != null)
            {
                switch (response.StatusCode)
                {
                    case HttpStatusCode.OK:
                        // Success – deserialize and return the list of workflow variables.
                        return this.Client.DeserializeJson<List<WorkflowVariable>>(response);
                    case HttpStatusCode.BadRequest:
                        // Failure – the endpoint URL could not access a workflow.
                        throw new ArgumentException("The request could not be processed.");
                    default:
                        throw new Exception("An unexpected error has occurred.");
                }
            }
            else
            {
                return null;
            }
        }
        /// <summary>
        /// Sends a message to start the Nintex workflow associated with the specified endpoint URL, optionally specifying 
        /// values for workflow variables defined by that workflow
        /// </summary>
        /// <param name="endpointUrl">
        /// The External Start endpoint URL associated with a Nintex workflow.
        /// </param>
        /// <param name="securityKey">
        /// The security key associated with the endpoint URL.
        /// </param>
        /// <param name="workflowVariables">
        /// The workflow variable values to use when starting the workflow.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// The X-CorrelationId: {GUID} of the request
        /// </returns>
        public string StartWorkflow(string endpointUrl, string securityKey, Dictionary<string, object> workflowVariables, TraceWriter log)
        {
            // If no workflow variable values are provided, send an empty request body; otherwise, send
            // a serialized JSON object, in which each workflow variable value is represented as a property value.
            string requestBody = "";
            if (workflowVariables != null)
            {
                JObject associationData = new JObject();
                foreach (string key in workflowVariables.Keys)
                {
                    JProperty value = new JProperty(key, workflowVariables[key]);
                    associationData.Add(value);
                }
                requestBody = associationData.ToString();
            }
            // Retrieve and configure the values used to calculate the digest value for the request.
            var path = new Uri(endpointUrl).AbsolutePath.ToLower();
            var httpMethod = HttpMethod.Post.ToString().ToLower();
            var nonce = Guid.NewGuid().ToString();
            var timestamp = DateTime.UtcNow.ToString("O");
            // Calculate and return the digest value for the request.
            var digest = CalculateDigest(securityKey, httpMethod, path, nonce, timestamp, requestBody);
            log.Info(digest);
            log.Info(timestamp);
            log.Info(nonce);
            // Specify the header values for the request.
            var headerValues = new Dictionary<string, string>();
            headerValues.Add("X-Api-Digest", digest);
            headerValues.Add("X-Api-Timestamp", timestamp);
            headerValues.Add("X-Api-Nonce", nonce);
            headerValues.Add("X-Api-Source", "ExternalStart");
            // Send the request to the endpoint URL. 
            HttpResponseMessage response = this.Client.Invoke(HttpMethod.Post, endpointUrl, headerValues,
                new StringContent(requestBody, Encoding.UTF8, "application/json"));
            
            if (response != null)
            {
                log.Info(response.StatusCode.ToString());
                switch (response.StatusCode)
                {
                    case HttpStatusCode.OK:
                        // Success – the message was successfully sent.
                        IEnumerable<string> correlationIds;
                        response.Headers.TryGetValues("X-CorrelationId", out correlationIds);
                        return correlationIds.FirstOrDefault();
                        break;
                    case HttpStatusCode.BadRequest:
                        // Failure – the endpoint URL could not access a workflow.
                        throw new ArgumentException("The request could not be processed.");
                    default:
                        throw new Exception("An unexpected error has occurred.");
                }
            }
            else
            {
                throw new Exception("An unexpected error has occurred.");
            }
        }
        /// <summary>
        /// Calculate the digest value used to authenticate requests for the External Start feature.
        /// </summary>
        /// <param name="securityKey">The security key associated with an External Start endpoint URL.</param>
        /// <param name="httpMethod">The HTTP method name, in lower case.</param>
        /// <param name="path">The absolute URI of the External Start endpoint URL, in lower case.</param>
        /// <param name="nonce">The nonce value.</param>
        /// <param name="timestamp">The date and time of the request, in ISO 8601 format.</param>
        /// <param name="requestBody">The serialized body of the request.</param>
        /// <returns>A keyed hash value, using the SHA-256 function, to be used as the Hash-based Authentication Code (HMAC) value for a request.</returns>
        public string CalculateDigest(string securityKey, string httpMethod, string path, string nonce,
            string timestamp, string requestBody)
        {
            // The data values are concatenated into a single string, in which each data value is delimited by 
            // a colon (:) character, which is then encoded as a UTF-8 byte array.
            var dataBytes = Encoding.UTF8.GetBytes(String.Join(":", httpMethod, path, nonce, timestamp, requestBody));
            // The security key is encoded as a UTF-8 byte array.
            var keyBytes = Encoding.UTF8.GetBytes(securityKey);
            // Using the HMACSHA256 object provided by .NET Framework, the
            // data values are hashed, using the security key, and any dashes are removed.
            using (var hasher = new HMACSHA256(keyBytes))
            {
                var hashBytes = hasher.ComputeHash(dataBytes);
                return BitConverter.ToString(hashBytes).Replace("-", "");
            }
        }
    }
    public class RestClient
    {
        public NetworkCredential Credential { get; set; }
        #region Constructors
        public RestClient()
        {
        }
        public RestClient(NetworkCredential credential)
        {
            this.Credential = credential;
        }
        #endregion
        #region Deserialization
        /// <summary>
        /// Deserialize the body of a specified HTTP response as an instance of a specified type.
        /// </summary>
        /// <typeparam name="T">The type into which to deserialize.</typeparam>
        /// <param name="restResponse">The HTTP response from which to deserialize.</param>
        /// <param name="jsonConverters">If needed, any custom JSON converters with which to deserialize.</param>
        /// <returns>An instance of the specified type, deserialized from the body of the specified HTTP response.</returns>
        internal T DeserializeJson<T>(HttpResponseMessage restResponse, JsonConverter[] jsonConverters = null)
        {
            return this.DeserializeJson<T>(restResponse.Content.ReadAsStringAsync().Result, jsonConverters);
        }
        /// <summary>
        /// Deserialize a specified JSON-encoded string as an instance of a specified type.
        /// </summary>
        /// <typeparam name="T">The type into which to deserialize.</typeparam>
        /// <param name="jsonString">The string from which to deserialize.</param>
        /// <param name="jsonConverters">If needed, any custom JSON converters with which to deserialize.</param>
        /// <returns>An instance of the specified type, deserialized from the body of the specified string.</returns>
        internal T DeserializeJson<T>(string jsonString, JsonConverter[] jsonConverters = null)
        {
            if (jsonConverters != null)
                return JsonConvert.DeserializeObject<T>(jsonString, jsonConverters);
            else
                return JsonConvert.DeserializeObject<T>(jsonString);
        }
        #endregion
        #region Serialization
        /// <summary>
        /// Serialize the specified object as a JSON-encoded string.
        /// </summary>
        /// <param name="value">The object from which to serialize.</param>
        /// <param name="jsonConverters">If needed, any custom JSON converters with which to serialize.</param>
        /// <returns>A JSON-encoded string that contains the serialization of the specified object.</returns>
        internal string SerializeJson(object value, JsonConverter[] jsonConverters = null)
        {
            if (jsonConverters != null)
            {
                return JsonConvert.SerializeObject(value, Formatting.Indented, jsonConverters);
            }
            return JsonConvert.SerializeObject(value, Formatting.Indented);
        }
        #endregion
        #region REST invocation
        /// <summary>
        /// Invokes the specified REST resource, using the specified HTTP method, optionally providing any specified header values and request content.
        /// </summary>
        /// <param name="operationMethod">The HTTP method with which to invoke the REST resource.</param>
        /// <param name="operationUrl">The operation URL with which to invoke the REST resource.</param>
        /// <param name="operationHeaders">A collection of header names and values to include when invoking the REST resource.</param>
        /// <param name="operationContent">The HTTP content to include when invoking the REST resource.</param>
        /// <returns></returns>
        internal HttpResponseMessage Invoke(HttpMethod operationMethod, 
            string operationUrl, 
            Dictionary<string, string> operationHeaders, 
            HttpContent operationContent)
        {
            HttpResponseMessage response = null;
            try
            {
                // Instantiate a new HttpClientHandler object and, if credentials are provided,
                // configure and include them.
                var clientHandler = new HttpClientHandler { PreAuthenticate = true };
                if (this.Credential == null)
                {
                    clientHandler.UseDefaultCredentials = true;
                }
                else
                {
                    clientHandler.Credentials = this.Credential;
                }
                // Instantiate a new HttpRequestMessage, using the specified HTTP method and operation URL,
                // for the request.
                var request = new HttpRequestMessage(operationMethod, operationUrl);
                // If header values are provided, add them to the request.
                // NOTE: The implementation is not optimal, but suffices for the sample.
                if (operationHeaders != null)
                {
                    foreach (var key in operationHeaders.Keys)
                    {
                        request.Headers.Add(key, operationHeaders[key]);
                    }
                }
                if (operationContent != null)
                {
                    request.Content = operationContent;
                }
                // Instantiate a new HttpClient, asynchronously invoke the REST resource,
                // and await the result. 
                using (var client = new HttpClient(clientHandler))
                {
                    response = client.SendAsync(request).GetAwaiter().GetResult();
                }
            }
            catch (Exception)
            {
                response = null;
            }
            // Return the resulting HttpResponseMessage.
            return response;
        }
        #endregion
    }
    public enum VariableTypes
    {
        Text,
        TextMultipleLine,
        Number,
        DateTime,
        Boolean
    }
    /// <summary>
    /// Models the information returned for workflow variables by the External Start feature.
    /// This class is used to deserialize that information for the purposes of the sample.
    /// </summary>
    public class WorkflowVariable
    {
        [JsonProperty("Name"), JsonRequired]
        public string Name { get; internal set; }
        [JsonProperty("Type"), JsonRequired]
        public VariableTypes Type { get; internal set; }
        [JsonProperty("Required"), JsonRequired]
        public bool Required { get; internal set; }
    }
 
 And then you'll need a project.json file:
 
 {
  "frameworks": {
    "net46":{
      "dependencies": {
        "Newtonsoft.Json": "8.0.3"
      }
    }
   }
}

Nintex Training (Advanced) – Its almost here!

New Nintex Courses are coming! 

Yup, Nintex is such a great product and it has been making so many strides in so many ways, I decided to go ahead and build a set of in-depth courses that cover just about everything you could possibly imagine about Nintex on-premises.  Currently the Nintex customer base is focused on 2013, but many people are moving to 2016 and Office 365 so another set of courses is coming in mid-2017 to cover those technologies.  The courses will become officially available in mid-February 2017, but I would love to have some testers that would like to get up to speed on Nintex quickly and get some free training if you give me feedback to cycle back in!

Cloud Services 

As some of you may be aware, Nintex has started to move its technology investments to cloud based applications.  The new Nintex Hawkeye, Nintex Workflow Cloud (competitor to PowerApps) and other items in the pipeline are designed to leverage the benefites of the cloud.  The new courses cover those technologies too (a first for Nintex courses)!

Nintex Subscription Pricing 

In addition, ACS has become a Nintex Partner so we can sell you Nintex subscriptions.  I'm not greedy so you can imagine the pricing will be pretty amazing, just drop me a line and I'll get you a quote!  Not only that, but I'll even throw in free training courses with the purchase in most cases!

You can find out more about the courses here:

http://www.architectingconnectedsystems.com/Nintex.aspx

Enjoy! 
Chris 

PowerStreamECM and Extreme Mode!

Fun, cool announcement to make.  

PowerStreamECM, a tool I have built for large scale ECM legacy migrations now supports EXTREME Mode for Office 365!

Extreme mode takes advantage of the Azure to O365 path using the content deployment API methodologies of SharePoint.  The fun interesting thing about Extreme mode is its designed to work with hard drive based deployments to Azure.  Sending 1GB, 10GB, 100GB to Azure is ok…try sending 30TB, or even 100TB to Azure over standard network lines….it just doesn't work.  Extreme mode allows you to partition your data across multiple drives and then send those drives to Azure for upload.  The entire process takes about 2-4 weeks.  

Once the data is loaded, you can then fire a secondary process to upload and modify any custom properties based on custom business rules to update the pre-loaded binaries.

Once uploaded, custom and 3rd party tools can apply the retention rules you are looking for to destroy old records or do eDiscovery.  Unfortunately, the only thing about uploading 30-100TB of data is that O365 can't handle it well for certain features.  That's where the experience of Hyper-scale design comes into play.  We have built a set of rules and software that allows you to do hyper-scale O365 deployments easily (although, time always plays a factor in hyperscale, just look at big data)!

If you have an old ECM system that has over 30TB of data that you want to move to O365, give me a shout.  I have seen it all at this point and can tell you what works and what doesn't!

Enjoy!
Chris