Marginal Cost Analysis
Custom SSRS Authentication Extended
private void Page_Load(object sender, EventArgs e)
{
//Your secret authentication sauce goes here..
//appHash should get dynamically generated from the app calling SSRS (ideally for each request if performant enough)
//ie.
//if (CheckAuth(System.Web.HttpContext.Current.Request.Cookies["origAppHash"].ToString()))
//if (CheckAuth(System.Web.HttpContext.Current.Session["otroAppHash"].ToString()))
if (System.Web.HttpContext.Current.Request.IsLocal)
FormsAuthentication.RedirectFromLoginPage("daylite", true);
}
private bool CheckAuth(string appHash)
{
//DecodeAndCryptoChecks on appHash
return true;
}
MS' Example uses Page_Load(); presumably Page_PreLoad() or Page_Init() would also work here- 'just an HttpRequest eval
EDI, RPC, SOAP, MQ, REST and Interoperability
EDI (Electronic Data Interchange): An exchange of data usually large in volume in comparison to other remote data transfer methods (batched records of 1000s vs. 1 record of JSON or 1 row of an RDBMS table), and usually done in conjunction with some kind of an ETL and/or Data Warehousing process. EDI is typically used for large, domain-specific transactions and the data transfer itself is performed over SFTP or another secure file transfer protocol and utilizes XSLT for data formatting. EDI files must adhere to strict ISO formatting specifications. This is helpful (and coincidentally adds a layer of complexity for hackers) when trying to ensure that a large number of disparate parties reporting data are all sending data in the right format as, if an EDI file's data format is wrong in any way, it won't be accepted at the destination.
RPC (Remote Procedure Call): Highly-coupled abstraction (if you can call it abstraction- it's really more of a video game accessory that only works on certain consoles) that essentially requires the client and server to be running the same program which, while once upon a time was feasible (and in some cases may be desirable for channel security), is not typically the ideal way to communicate openly. However, for closed, secure communications, RPC is still very much a part of the many technologies that facilitate secure messaging in applications like Telegram, Signal and the like.
"Next Big" Software Religiosity and The Go-nowhere Rush
There is far too much religious extremism in information technology these days. And there have always been camps (extreme anti-Microsoft sentiment or its sad corporate counterpart: disdain, fear and suspicion of all things open-source)- but these days it has gotten to the point where sensible, cheap, reliable, proven solutions that everyone on the team understands- are thrown out in favor of chasing the next big thing that some bigshot at some big conference declared was going to be the next, next, next "big thing".
This image does have its merits..
Getting Familiar with Microsoft Azure
Then it's amazing.
Starting out
The idea of any cloud provider is to enable IaaS, SaaS and PaaS among other XaaS's. Instead of having to provision physical machines, network equipment and associated hardware, and go out to dozens of different vendors to manage service agreements for the various services a company uses- nowadays a company can move most of that distributed mess into their own private cloud and just manage everything in one place.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"adminUsername": {
"type": "string",
"metadata": {
"description": "Username for the Virtual Machine."
}
},
"adminPassword": {
"type": "securestring",
"minLength": 12,
"metadata": {
"description": "Password for the Virtual Machine."
}
},
"dnsLabelPrefix": {
"type": "string",
"defaultValue": "[toLower(concat(parameters('vmName'),'-', uniqueString(resourceGroup().id, parameters('vmName'))))]",
"metadata": {
"description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
}
},
"publicIpName": {
"type": "string",
"defaultValue": "myPublicIP",
"metadata": {
"description": "Name for the Public IP used to access the Virtual Machine."
}
},
"publicIPAllocationMethod": {
"type": "string",
"defaultValue": "Dynamic",
"allowedValues": [
"Dynamic",
"Static"
],
"metadata": {
"description": "Allocation method for the Public IP used to access the Virtual Machine."
}
},
"publicIpSku": {
"type": "string",
"defaultValue": "Basic",
"allowedValues": [
"Basic",
"Standard"
],
"metadata": {
"description": "SKU for the Public IP used to access the Virtual Machine."
}
},
"OSVersion": {
"type": "string",
"defaultValue": "2019-Datacenter",
"allowedValues": [
"2008-R2-SP1",
"2012-Datacenter",
"2012-R2-Datacenter",
"2016-Nano-Server",
"2016-Datacenter-with-Containers",
"2016-Datacenter",
"2019-Datacenter",
"2019-Datacenter-Core",
"2019-Datacenter-Core-smalldisk",
"2019-Datacenter-Core-with-Containers",
"2019-Datacenter-Core-with-Containers-smalldisk",
"2019-Datacenter-smalldisk",
"2019-Datacenter-with-Containers",
"2019-Datacenter-with-Containers-smalldisk"
],
"metadata": {
"description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version."
}
},
"vmSize": {
"type": "string",
"defaultValue": "Standard_D2_v3",
"metadata": {
"description": "Size of the virtual machine."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
},
"vmName": {
"type": "string",
"defaultValue": "simple-vm",
"metadata": {
"description": "Location for all resources."
}
}
},
"variables": {
"storageAccountName": "[concat('bootdiags', uniquestring(resourceGroup().id))]",
"nicName": "myVMNic",
"addressPrefix": "10.0.0.0/16",
"subnetName": "Subnet",
"subnetPrefix": "10.0.0.0/24",
"virtualNetworkName": "MyVNET",
"subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]",
"networkSecurityGroupName": "default-NSG"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2020-06-01",
"name": "[parameters('publicIPName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('publicIpSku')]"
},
"properties": {
"publicIPAllocationMethod": "[parameters('publicIPAllocationMethod')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsLabelPrefix')]"
}
}
},
{
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2020-06-01",
"name": "[variables('networkSecurityGroupName')]",
"location": "[parameters('location')]",
"properties": {
"securityRules": [
{
"name": "default-allow-3389",
"properties": {
"priority": 1000,
"access": "Allow",
"direction": "Inbound",
"destinationPortRange": "3389",
"protocol": "Tcp",
"sourcePortRange": "*",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*"
}
}
]
}
},
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2020-06-01",
"name": "[variables('virtualNetworkName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
],
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetPrefix')]",
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]"
}
}
}
]
}
},
{
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2020-06-01",
"name": "[variables('nicName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPName'))]",
"[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2018-10-01",
"name": "[parameters('vmName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
"[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"storageProfile": {
"imageReference": {
"publisher": "MicrosoftWindowsServer",
"offer": "WindowsServer",
"sku": "[parameters('OSVersion')]",
"version": "latest"
},
"osDisk": {
"createOption": "FromImage",
"managedDisk": {
"storageAccountType": "StandardSSD_LRS"
}
},
"dataDisks": [
{
"diskSizeGB": 1023,
"lun": 0,
"createOption": "Empty"
}
]
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true,
"storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))).primaryEndpoints.blob]"
}
}
}
}
],
"outputs": {
"hostname": {
"type": "string",
"value": "[reference(parameters('publicIPName')).dnsSettings.fqdn]"
}
}
}
For a trial period of (currently 12 months) most all of the really useful stuff is free (be careful not to accidentally deploy Azure Co$mos though...😳 ...that is not free, and that is not cheap). After the trial period, the cost was still relatively cheap for the services that I use most in Azure (an App Service hosting a handful of .NET Core apps with SSL, 1 powerful virtual machine, a DNS zone, a vNet)- all for about $30/month.
"Arc works by running an agent on your non-azure resources; this is a service on VM's and a Kubernetes pod on Kubernetes cluster. Once you install this service, the machine registers with Azure and is ready for management." -samcogan.com
Monitor usage
Azure, like any cloud provider, forces you to take a fine-grain look at every single resource you are using. It is amazing how much stuff we don't actually use.
It is only when you begin to pay for usage of each resource and see the numbers rising daily do you really understand how much you are utilizing your various resources.
Powerful computing machines began as a timeshare because of the realization that it is madness to let expensive machines sit idle. And though the resources to share and provision among users has become far more complex, we are returning to that model.
Conclusion
Whether you use Azure to explore different kinds of technology or to implement an IT infrastructure completely in the cloud to connect and supercharge your applications and/or workforce- the tech is now there and the costs are comparable to AWS.
My two criticisms of Azure are that (1) Azure seems to excessively spotlight/push certain features forward and other features (many practical, free and cheap things you would think are "essential"- like setting up DNS zones) remain sort of in the shadows/awaiting help links waiting to be discovered... And (2), sadly other things, little things like logging analytic insights that you would think are free are in fact Azure resources that charge RUs. 😕
These aspects suck but are tolerable in light of all of the awesome functionality Azure provides.
Microsoft continues to improve an industry-leading cloud platform that executives, management, engineers, developers, and system admins alike can all learn to love. 💖
PowerShell Commands
#check security levelGet-ExecutionPolicy
#elevate security access levelSet-ExecutionPolicy Unrestricted
#get information on any serviceGet-Service -Name PhoneSvc
#get the same log info seen in eventvwrGet-EventLog -Log "Application"
#get process informationGet-Process -ComputerName MYCOMPUTER
#stop process like cmd.exe killStop-Process -Name “notepad”
#get drive information of the drives connected to the current PS sessionGet-PSDrive
#get information on any powershell cmdlt, function or moduleGet-Help -Name Streaming
#get all the installed commandsGet-Command
#connect to your azure account with the "az" azure cmdlt
Connect-AzureRmAccount
#upload blob content to storage
Set-AzStorageBlobContent -File "D:\_TestImages\Image002.png" `
-Container $containerName `
-Blob "Image002.png" `
-Context $ctx
-StandardBlobTier Cool
#download blob content from storage
Get-AzStorageBlobContent -Blob "Image002.png" `
-Container $containerName `
-Destination "D:\_TestImages\Downloads\" `
-Context $ctx
#stop a sql server instanceStop-SqlInstance -ServerInstance MSSQL01
#clear screenClear-Host
#pingTest-NetConnection
#telnetTest-NetConnection -Port
#tracertTest-NetConnection -TraceRoute
#ipconfigGet-NetIPAddress
#nslookupResolve-DnsName -Name "Hostname"
#netstatGet-NetTCPConnection
#flushdnsClear-DnsClientCache
#ip release/renewInvoke-Command -ComputerName -ScriptBlock {ipconfig /release}
Invoke-Command -ComputerName -ScriptBlock {ipconfig /renew}
#disable/enable network cardInvoke-Command -ComputerName -ScriptBlock {ipconfig /release}
Invoke-Command -ComputerName -ScriptBlock {ipconfig /renew}
#export a list to .csv fileGet-Service | Export-CSV c:\20200912_ServiceSnapshot.csv
#be more selective with Select-Object module and pipe that to the csvGet-Service | Select-Object Name, Status | Export-CSV c:\20200912_ServiceStatusSnapshot.csv
#get event information and pipe method (of each log event) info to the consoleGet-EventLog -log system | gm -MemberType Methods
#get a process and stop itGet-Process notepad | Stop-Process
#delete all files matching some Regex patternGet-ChildItem $Path | Where{$_.Name -Match "someFileName.txt"} | Remove-Item
Useful Calculators
The following are links to some very useful calculators and en/decoding tools that can help you do anything from binary encoding/decoding, encryption/decryption to identifying supernet and subnet (the components an IP's network and host portions) information by IP address and number of masking bits applied.
- https://www.devglan.com/online-tools/aes-encryption-decryption
- http://www.unit-conversion.info/texttools/ascii/
- http://www.unit-conversion.info/texttools/convert-text-to-binary/#data
- https://www.calculator.net/binary-calculator.html
- https://www.calculator.net/ip-subnet-calculator.html
- https://www.calculator.net/standard-deviation-calculator.html
- https://onlinehextools.com/xor-hex-numbers
- https://www.calculator.net/random-number-generator.html
Programming Language Origins and Paradigms
The following charts (1) outline the origins of some of the most well known languages from the outset of computing up to 2001 and (2) illustrate the primary motivations and programmatic structure of several languages.
A brief history of computing languages up to 2001
Many languages, many different ways of creating software suited for various purposes
Small Multiples (are awesome)
"A small multiple (sometimes called trellis chart, lattice chart, grid chart, or panel chart) is a series of similar graphs or charts using the same scale and axes, allowing them to be easily compared. It uses multiple views to show different partitions of a dataset."
Read any serious visual communication guide and it will invariably highlight this powerful tool we have at our disposal when we have the data (we almost always have the data).
A pair of Small Multiples example quite pertinent to the current times followed by some other good ones:
Locations - Google Maps API, ASP.NET Core and SQL Server
<script>
function initMap() {
var map = new google.maps.Map(
document.getElementById('map'),
{
center: new google.maps.LatLng(@Model.CenterLat, @Model.CenterLong),
zoom: 8
}
);
var pins = @Html.Raw(Json.Serialize(@Model.Locations));
for (var i = 0; i < pins.length; i++) {
var myLatLng = {
lat: pins[i].lat,
lng: pins[i].long
};
addMarker(myLatLng, map, pins[i]);
}
}
function addMarkerAsync(location, map) {
new google.maps.Marker({
position: location,
title: 'Home Center',
});
marker.setMap(map);
}
function addMarker(location, map, pin) {
var marker = new google.maps.Marker({
position: location,
title: '...something dyanmic...',
});
var infowindow = new google.maps.InfoWindow({
content: ''
});
function AsyncDisplayString() {
$.ajax({
type: 'GET',
url: '/Home/GetLocationModalInfo',
dataType: "HTML",
contentType: 'application/json',
traditional: true,
data: pin,
success: function (result) {
debugger;
infowindow.setContent('<div style="background-color:#000000;">' + result + '</div>');
infowindow.open(map, marker);
},
error: function (arg) {
alert('Error');
}
});
}
google.maps.event.addListener(marker, 'click', function () {
AsyncDisplayString(map, marker)
});
marker.setMap(map);
}
</script>
[HttpPost]
public IActionResult AddLocation(LocationModel location)
{
string address = location.StreetAddress1.Replace(" ", "+") + "," + location.City.Replace(" ", "+") + "," + location.State.Replace(" ", "+");
MapPoint coords = _locationService.GetLatLongFromAddress(address);
location.Lat = (decimal)coords.Latitude;
location.Long = (decimal)coords.Longitude;
using (var db = new SqlConnection(_configuration.GetConnectionString("DefaultConnection")))
{
db.Open();
string sql = @"INSERT INTO [Locations].[dbo].[Locations] ([Name], [Contact], [Email], [Website], [Phone], [StreetAddress1], [StreetAddress2], [City]"
+ ",[State], [Zip], [LocationContact], [PrimaryContact], [Notes], [Type], [Lat], [Long], [Petitions], [Flyers], [Posters], [LastPickUpDateTime], [LastOutOfStockDateTime], LastDropoffDateTime"
+ ",[AllTimeOutofStock],[Unsupportive],[VolunteerInterest])"
+ " VALUES ('" + location.Name + "','" + location.Contact + "','" + location.Email + "','" + location.Website + "','" + location.Phone + "','" + location.StreetAddress1 + "','" + location.StreetAddress1 + "','" + location.City + "'"
+ ",'" + location.State + "','" + location.Zip + "', -1, -1,'" + location.Notes + "', 1, " + location.Lat + "," + location.Long + "," + location.Petitions + "," + location.Flyers + "," + location.Posters + ",'" + location.LastPickUpDateTime + "','" + location.LastOutOfStockDateTime + "','" + location.LastDropoffDateTime + "', 0, 0, 1) " + ";";
db.Execute(sql);
}
var model = GetDefaultMapView();
model.KeyString = _configuration["MapsAPIKey"].ToString();
return View("Map", model);
}
ChartJS for Data Vizualiizations
You can do some really neat and dynamic stuff with ChartJS.
I have used a lot of charting frameworks, and it does not get more flexible or simple than this:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
</head>
<body>
<div>
<canvas id="myChart" style='background-color:darkgray; width:100%; height:100%;'></canvas>
</div>
<script>
var ctx = document.getElementById('myChart').getContext('2d');;
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['16_Qtr1', '16_Qtr2', '16_Qtr3', '16_Qtr4', '17_Qtr1', '17_Qtr2', '17_Qtr3', '17_Qtr4', '18_Qtr1', '18_Qtr2', '18_Qtr3', '18_Qtr4', '19_Qtr1', '19_Qtr2', '19_Qtr3', '19_Qtr4', '20_Qtr1', '20_Qtr2', '20_tr3', '20_Qtr4', '21_Qtr1', '21_Qtr2', '21_Qtr3', '21_Qtr4','22_Qtr1', '22_Qtr2', '22_Qtr3', '22_Qtr4', '23_Qtr1', '23_Qtr2', '23_tr3', '23_Qtr4'],
datasets: [{
label: 'Some random quartley demo data..',
backgroundColor: 'black',
borderColor: 'lime',
data: [40.2, 72.88, 47.1, 22, 54.43, 52.18, 17.1, 52, 67.2, 54.88, 64.1, 78, 67.2, 55.88, 58.1, 57, 50.2, 52.88, 57.1, 62, 74.43, 62.18, 67.1, 72, 77.2, 74.88, 74.1, 78, 77.2, 75.88, 78.1, 77, 70.2, 72.88, 77.1, 62, 64.43, 62.18, 67.1, 72, 67.2, 54.88, 44.1, 28, 27.2, 25.88, 38.1, 37, 40.2, 42.88, 44.1, 52, 54.43, 52.18, 67.1, 82, 87.2, 84.88, 84.1, 88, 87.2, 95.88, 108.1, 127]
}]
},
"options": {
"legend": {"position": "bottom"}
}
});
</script>
</body>
</html>
Reference: https://www.chartjs.org/
SSRS REST API v2
The SSRS API v2 has far more functionality than v1, but they essentially work the same. You must be authenticated to the SSRS report server you are targeting (localhost in this case) to make web GET/POST requests to the API.
Once auth'd you can push and pull any useful SSRS data pretty easily to make SSRS do some pretty cool things it can't do out of the box..
You can get a collection of SSRS catalog items as in the example above (folders, reports, KPIs) by just specifying the action name, or you can select an individual item by putting the item GUID in parenthesis in the API request URL:
Common Useful SSRS API v2 Actions:
- Reports
- Datasets
- Data Sources
- Folders
- Schedules
- Subscriptions
- Comments
- KPIs
- CatalogItems (everything)
Example of a .NET Standard library with an HttpService abstacting the SSRS API calls:
namespace ExtRS
{
public class SSRSHttpService
{
const string ssrsApiURI = "https://localhost/reports/api/v2.0";
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
public async Task<GenericItem> GetReportAsync(Guid id)
{
client.BaseAddress = new Uri(ssrsApiURI + string.Format("/reports({0})", id));
var response = await client.GetAsync(client.BaseAddress);
var odata = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<GenericItem>(odata);
}
}
}
A very basic class designed to demonstrate using SSRS API Response to create a .NET object:
using Newtonsoft.Json;
using System.Collections.Generic;
namespace ExtRS
{
public class GenericItem
{
[JsonProperty("@odata.context")]
public string ODataContext { get; set; }
[JsonProperty("Id")]
public string Id { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Path")]
public string Path { get; set; }
}
}
using System;
using System.Web.Mvc;
using System.Threading.Tasks;
using ExtRS;
namespace Daylite.Controllers
{
public class ReportsController : Controller
{
public SSRSHttpService service = new SSRSHttpService();
public async Task<ViewResult> GetReportsAsync()
{
return View("Index", await service.GetReportsAsync());
}
public async Task<ViewResult> GetFoldersAsync()
{
APIGenericItemsResponse result = await service.GetFoldersAsync();
return View("Index", result);
}
public async Task<ViewResult> GetReportAsync(Guid id)
{
GenericItem result = await service.GetReportAsync(id);
return View("Index", result);
}
}
}
Reference: https://github.com/Microsoft/Reporting-Services/tree/master/APISamples