Last active
December 28, 2023 20:15
-
-
Save gevaertw/5048676da755dccfabe1947050b24fc8 to your computer and use it in GitHub Desktop.
#jArchi #AzureServiceMap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
---------------------------------------------------About------------------------------------------------------------------------------------------------------------------ | |
Version 1 | |
This script loads archi elements from an Azure service map into archimate. It Shows the network communication between servers as found by azure service map. | |
All will be drawn on an model but without considering the data already in the model. In V2 (if that ever gets created) I will work using existing models and the data it has | |
Use the query below in service map to get the input file. Do not change the fields or field order from the query, you can change the fileters if you like. | |
I highly recomend you to filter out any connection that you consider irrellevant or junk. Running an input file of 2000 lines takes around 90 seconds on my laptop. | |
for a full explaination of how to use this script refer to: | |
https://gevaertw.wordpress.com/2019/03/17/use-jarchi-to-draw-archi-relations-from-azure-service-map/ | |
----------------------------------------------------Start of log analytics query------------------------------------------------------------------------------------------------------------------ | |
VMConnection | |
| where Direction == "outbound" | |
| where DestinationIp != "127.0.0.1" | |
| distinct Computer, ProcessName, SourceIp, DestinationIp, Protocol, DestinationPort, RemoteDnsCanonicalNames, RemoteCountry) | |
----------------------------------------------------End of log analytics query------------------------------------------------------------------------ | |
I have reused file opening code from | |
https://gist.github.com/smileham/1e57a5946235e780dee5a824f664aa3d | |
*/ | |
//Global stuff | |
var fakeGUID = "00000000-AAAA-AAAA-AAAA-000000000000"; | |
var archiPropertyCreatedBy = "Azure service map to Archi script" // everything creted by this script recieves this property, this helps filerering out in Archi | |
//----------------------------------------------------Object constructors--------------------------------------------------------------------------------------------------------------- | |
function LogLine(computer_Parameter, processName_Parameter, sourceIP_Parameter, destinationIP_Parameter, protocol_Parameter, destinationPort_Parameter, remoteDnsCanonicalNames_Parameter, remoteCountry_Parameter, sourceComputerGUID_Parameter,remoteComputerGUID_Parameter) | |
{ | |
this.lineSourceComputerName = computer_Parameter; | |
this.lineProcessName = processName_Parameter; | |
this.lineSourceIp = sourceIP_Parameter; | |
this.lineDestinationIp = destinationIP_Parameter; | |
this.lineProtocol = protocol_Parameter; | |
this.lineDestinationPort = destinationPort_Parameter; | |
this.lineRemoteComputerName = remoteDnsCanonicalNames_Parameter; | |
this.lineRemoteCountry = remoteCountry_Parameter; | |
this.lineSourceComputerGUID = sourceComputerGUID_Parameter; | |
this.lineRemoteComputerGUID = remoteComputerGUID_Parameter; | |
} | |
function ElementsLine(id_Parameter,name_Parameter,type_Parameter,elementIP_Parameter) | |
{ | |
this.elementID = id_Parameter; | |
this.elementName = name_Parameter; | |
this.elementType = type_Parameter; | |
this.elementIP = elementIP_Parameter; | |
} | |
//----------------------------------------------------Functions used by the script----------------------------------------------------------------------------------------------------- | |
function isInElementsArray (inputElementArray_Parameter, elementName_Parameter) | |
{ | |
//this function checks if a given element name is in the given elements array | |
a = 0; | |
elementFound = false; | |
//console.log("Array length: " + inputElementArrayP.length); | |
while (elementFound == false && a < inputElementArray_Parameter.length) | |
{ | |
//console.log("Searching array in row: " + a); | |
if (inputElementArray_Parameter[a].elementName.toString() == elementName_Parameter) | |
{ | |
elementFound = true; | |
//console.log("found element in row: " + a); | |
} | |
a++; | |
} | |
return elementFound; | |
} | |
//----------------------------------------------------Archi specific functions --------------------------------------------------------------------------------------------------------- | |
function archiElementObjectFromName(elementName_Parameter) | |
{ | |
//This function accepts the name of an element and returns the coresponding ARCHI object. | |
searchString = ".".concat(elementName_Parameter); | |
archiCollection = $(searchString); | |
return archiCollection.first(); // we assume unique names in the model | |
} | |
function archiCreateElementOnModel (elementName_Parameter, elementType_Parameter,folder_Parameter) | |
{ | |
//This function creates an element in Archi and returns the GUID | |
var d = new Date(); | |
var element = model.createElement(elementType_Parameter,elementName_Parameter, folder_Parameter); | |
element.prop ("Created By", archiPropertyCreatedBy, false); | |
element.prop ("Created On", d.toUTCString(), false); | |
GUID = element.id; | |
return GUID; | |
} | |
function archiCreateRelationOnModel (realtionName_Parameter, sourceName_Parameter, targetName_Parameter, processName_Parameter,folder_Parameter) | |
{ | |
//This function creates a relationship between 2 objects, it returns the GUID of the created relation | |
var d = new Date(); | |
var realationType = "Flow-relationship"; // I use the lowest type of relation here. | |
sourceArchiObject = archiElementObjectFromName(sourceName_Parameter); | |
targetArchiObject = archiElementObjectFromName(targetName_Parameter); | |
var relationship = model.createRelationship(realationType, realtionName_Parameter , sourceArchiObject, targetArchiObject,folder_Parameter); | |
relationship.prop("Created By", archiPropertyCreatedBy, false); | |
relationship.prop("Created On", d.toUTCString(), false); | |
relationship.prop("Originating Process", processName_Parameter, false); | |
return relationship.id; | |
} | |
//----------------------------------------------------Functions that help debugging --------------------------------------------------------------------------------------------------------- | |
function showLineObjectArray (inputArray_Parameter) | |
{ | |
//this function shows the array content of the file. I use it to help debugging | |
aLength = inputArray_Parameter.length; | |
console.log("the array has " + aLength + " lines"); | |
for (arrayDisplayCount = 0; arrayDisplayCount< aLength; arrayDisplayCount++) | |
{ | |
console.log( | |
arrayDisplayCount +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineSourceComputerName +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineProcessName +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineSourceIp +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineDestinationIp +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineProtocol +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineDestinationPort +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineRemoteComputerName +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineRemoteCountry +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineSourceComputerGUID +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].lineRemoteComputerGUID | |
); | |
} | |
} | |
function showLineObjectArrayLine (inputArray_Parameter,line_Parameter) | |
{ | |
//this function shows the array line of the file. I use it to help debugging | |
console.log( | |
"Line: "+ line_Parameter +"\n"+ | |
"lineSourceComputerName: "+ inputArray_Parameter[LineP].lineSourceComputerName +"\n"+ | |
"lineProcessName: "+ inputArray_Parameter[LineP].lineProcessName +"\n"+ | |
"lineSourceIp: "+ inputArray_Parameter[LineP].lineSourceIp +"\n"+ | |
"lineDestinationIp: "+ inputArray_Parameter[LineP].lineDestinationIp +"\n"+ | |
"lineProtocol: "+ inputArray_Parameter[LineP].lineProtocol +"\n"+ | |
"lineDestinationPort: "+ inputArray_Parameter[LineP].lineDestinationPort +"\n"+ | |
"lineRemoteComputerName: "+ inputArray_Parameter[LineP].lineRemoteComputerName +"\n"+ | |
"lineRemoteCountry: "+ inputArray_Parameter[LineP].lineRemoteCountry +"\n"+ | |
"lineSourceComputerGUID: "+ inputArray_Parameter[LineP].lineSourceComputerGUID +"\n"+ | |
"lineRemoteComputerGUID: "+ inputArray_Parameter[LineP].lineRemoteComputerGUID +"\n" | |
); | |
} | |
function showElementObjectArray (inputArray_Parameter) | |
{ | |
//this function shows the array content of the Archi elements. I use it to help debugging | |
aLength = inputArray_Parameter.length; | |
console.log("the array has " + aLength + " lines"); | |
for (arrayDisplayCount = 0; arrayDisplayCount< aLength; arrayDisplayCount++) | |
{ | |
console.log( | |
arrayDisplayCount +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].elementID +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].elementName +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].elementType +"|-|"+ | |
//inputArrayP[arrayDisplayCount].elementDocumentation +"|-|"+ | |
inputArray_Parameter[arrayDisplayCount].elementIP | |
); | |
} | |
} | |
function showElementObjectArrayLine (inputArray_Parameter,line_Parameter) | |
{ | |
//this function shows the array line of Archi elements.. I use it to help debugging | |
console.log( | |
"Line: "+ line_Parameter +"\n"+ | |
"elementID: "+ inputArray_Parameter[line_Parameter].elementID +"\n"+ | |
"elementName: "+ inputArray_Parameter[line_Parameter].elementName +"\n"+ | |
"elementType: "+ inputArray_Parameter[line_Parameter].elementType +"\n"+ | |
//"elementDocumentation: "+ inputArrayP[lineP].elementDocumentation +"\n"+ | |
"elementIP: "+ inputArray_Parameter[line_Parameter].elementIP +"\n" | |
); | |
} | |
function showTime () | |
{ | |
// this function shows the current time | |
var today = new Date(); | |
var h = today.getHours(); | |
var m = today.getMinutes(); | |
var s = today.getSeconds(); | |
return h +":"+m +":"+s; | |
} | |
//----------------------------------------------------start of script ----------------------------------------------------------------------------------------------------------------------------------------------- | |
//First the file must be read | |
console.show(); | |
console.clear(); | |
console.log(showTime() + "> Import CSV"); | |
var startTime = Date.now(); | |
var filePath = window.promptOpenFile({ title: "Open CSV", filterExtensions: ["*.CSV"], fileName: "default.archimate" }); | |
if (filePath) | |
{ | |
var FileReader = Java.type("java.io.FileReader"); | |
var theCSVFile = new FileReader(filePath); | |
var theCSV =""; | |
var data = theCSVFile.read(); | |
while(data != -1) { | |
var theCharacter = String.fromCharCode(data); | |
theCSV+=theCharacter; | |
data = theCSVFile.read(); | |
} | |
theCSVFile.close(); | |
} | |
console.log(showTime() + "> File Loaded"); | |
//cleanup file | |
var allLinesUnclean = theCSV.toString(); //the entire file as string | |
//remove all " [] form the string, it is junk | |
allLinesUnclean1 = allLinesUnclean.replace(/\"/g,""); | |
allLinesUnclean2 = allLinesUnclean1.replace(/\[/g,""); | |
allLines = allLinesUnclean2.replace(/\]/g,""); | |
var lines = allLines.split('\n'); //an array of all lines in the file as strings | |
var headerLine = lines[0].split(','); //the first line | |
var rowCount = lines.length; //The amount of lines parsed | |
var colCount = headerLine.length; //The amount of colums parsed | |
console.log (showTime() + ">There are " + rowCount + " lines in the file."); | |
//put the data, as objects in an array, row by row | |
var logAnalyticsArray = []; | |
var lineReader = new String(); //Will contain current row | |
var colSplit = new Array([colCount]); //Will contain all colls from row | |
var colReader = new String(); //Will contain current coll | |
console.log(showTime() + ">Start parsing"); | |
for (rows = 0; rows < rowCount -1 ; rows++) // -1 to skip last line it is usualy the result of a \n\r at the end file somewhere | |
{ | |
//lineReader = lines[rows]; | |
//console.log("Working on line " + lineReader); | |
colSplit = lines[rows].split(','); | |
colComputer = colSplit[0]; | |
colProcessName = colSplit[1]; | |
colSourceIP = colSplit[2]; | |
colDestinationIP = colSplit[3]; | |
colProtocol = colSplit[4]; | |
colDestinationPort = colSplit[5]; | |
colRemoteDnsCanonicalNames = colSplit[6]; | |
colRemoteCountry = colSplit[7]; | |
colSourceComputerGUID = fakeGUID; | |
colRemoteComputerGUID = fakeGUID; | |
//last element (country) seems to contain newline characters, they must be removed or they create crappy info | |
colRemoteCountry = colRemoteCountry.replace(/\n/g,""); | |
colRemoteCountry = colRemoteCountry.replace(/\r/g,""); | |
//if the destination DNS could not be resolved it is null, in that case the put the destination IP there | |
if (colRemoteDnsCanonicalNames == null ||colRemoteDnsCanonicalNames == "") | |
{ | |
colRemoteDnsCanonicalNames = colDestinationIP; | |
//console.log("Row with unknown hostname: "+ rows + " added: "+ colRemoteDnsCanonicalNames ); | |
} | |
/* | |
else | |
{ | |
console.log("Row has valid hostname: |" + colRemoteDnsCanonicalNames + "|"); | |
} | |
*/ | |
//if the destinatin country could not be resolved it is null, in that case we put unknown location | |
if (colRemoteCountry == null || colRemoteCountry == "") | |
{ | |
//this does not seem to work well | |
colRemoteCountry = "Unknown location"; | |
//console.log("Row with unknown country: "+ rows + " added "+ colRemoteCountry); | |
} | |
/* | |
else | |
{ | |
console.log("Row has valid country: |"+ colRemoteCountry + "|"); | |
} | |
*/ | |
//Write the line object into the array | |
logAnalyticsArray[rows] = new LogLine ( | |
colComputer, | |
colProcessName, | |
colSourceIP, | |
colDestinationIP, | |
colProtocol, | |
colDestinationPort, | |
colRemoteDnsCanonicalNames, | |
colRemoteCountry, | |
colSourceComputerGUID, | |
colRemoteComputerGUID | |
); | |
} | |
//Cleanup array of object | |
logAnalyticsArray.splice(0,1); //first 1 rows is blankline & header, we don't need it | |
//showLineObjectArray (logAnalyticsArray); //show content after parsing | |
console.log(showTime() + ">Done parsing"); | |
/* | |
----------------------------------------------------Put Nodes on the model.---------------------- | |
*/ | |
var nodesToCreateOnModel = []; | |
// if the array is initaly empty we cannot compare its content later on so lets give it something. It is overwritten later on.. | |
nodesToCreateOnModel.push (new ElementsLine ( | |
"ID", | |
"Name", | |
"Node", | |
"IP Address" | |
)); | |
//Create a folder to put the nodes in | |
var folder = $("folder.Technology & Physical").first(); | |
var importTechSubFolder = folder.createFolder("Log Analytics Import"); | |
//loop trough all lines in array | |
for (logAnalyticsComputerSearchCounter = 0; logAnalyticsComputerSearchCounter < logAnalyticsArray.length; logAnalyticsComputerSearchCounter++) | |
{ | |
//console.log(logAnalyticsComputerSearchCounter); | |
sourceName = logAnalyticsArray[logAnalyticsComputerSearchCounter].lineSourceComputerName.toString(); | |
remoteName = logAnalyticsArray[logAnalyticsComputerSearchCounter].lineRemoteComputerName.toString(); | |
sourceIp = logAnalyticsArray[logAnalyticsComputerSearchCounter].lineSourceIp.toString(); | |
remoteIP = logAnalyticsArray[logAnalyticsComputerSearchCounter].lineDestinationIp.toString(); | |
sourceGUID = fakeGUID; | |
remoteGUID = fakeGUID; | |
/* | |
console.log( | |
logAnalyticsComputerSearchCounter + "|" + | |
"sourceName: "+ sourceName + ";" + | |
"remoteName: "+ remoteName +";" + | |
"sourceIp: "+ sourceIp +";" + | |
"remoteIP: "+ remoteIP +";" + | |
"sourceGUID: "+ sourceGUID +";" + | |
"remoteGUID: "+ remoteGUID+";" | |
); | |
*/ | |
//If source name is not in the array add it, else skip it | |
if (isInElementsArray(nodesToCreateOnModel,sourceName) == false) | |
{ | |
//sourcename not part of array, add it to the array | |
nodesToCreateOnModel.push (new ElementsLine ( | |
fakeGUID, | |
sourceName, | |
"Node", | |
sourceIp | |
)); | |
//immidiatlty create it on the Archi model, we get back the real GUID | |
sourceGUID = archiCreateElementOnModel(sourceName,"Node", importTechSubFolder); | |
//console.log("just created Source element: "+sourceGUID); | |
//Add GUID to nodes & Lines Array | |
nodesToCreateOnModel[nodesToCreateOnModel.length-1].elementID = sourceGUID; | |
logAnalyticsArray[logAnalyticsComputerSearchCounter].lineSourceComputerGUID = sourceGUID; | |
} | |
else | |
{ | |
//it is part of the array so we only enrich the line with the source GUID | |
logAnalyticsArray[logAnalyticsComputerSearchCounter].lineSourceComputerGUID = archiElementObjectFromName(sourceName).id; | |
} | |
//If destination name is not in the array add it, else skip it | |
if (isInElementsArray(nodesToCreateOnModel,remoteName) == false) | |
{ | |
//remoteName not part of array, add it to the array | |
nodesToCreateOnModel.push (new ElementsLine ( | |
fakeGUID, | |
remoteName, | |
"Node", | |
remoteIP | |
)); | |
//immidiatlty create it on the Archi model, we get back the real GUID | |
remoteGUID = archiCreateElementOnModel(remoteName,"Node",importTechSubFolder); | |
//console.log("just created Target element: "+ remoteGUID); | |
//Add GUID to nodes Array | |
nodesToCreateOnModel[nodesToCreateOnModel.length - 1].elementID = remoteGUID; | |
logAnalyticsArray[logAnalyticsComputerSearchCounter].lineRemoteComputerGUID = remoteGUID; | |
} | |
else | |
{ | |
//it is part of the array so we only enrich the line with the remotecomputer GUID | |
logAnalyticsArray[logAnalyticsComputerSearchCounter].lineRemoteComputerGUID = archiElementObjectFromName(remoteName).id; | |
} | |
} | |
//showElementObjectArray (nodesToCreateOnModel); | |
//showLineObjectArray(logAnalyticsArray); | |
console.log(showTime() + ">New elements created"); | |
/* | |
----------------------------------------------------put relations on the model---------------------- | |
*/ | |
//Create a folder to put the realations in | |
var folder = $("folder.Relations").first(); | |
var importRelationsSubFolder = folder.createFolder("Log Analytics Import"); | |
for (logAnalyticsRelationCreator = 0; logAnalyticsRelationCreator < logAnalyticsArray.length; logAnalyticsRelationCreator++) | |
{ | |
sourceObjName = logAnalyticsArray[logAnalyticsRelationCreator].lineSourceComputerName.toString(); | |
remoteObjName = logAnalyticsArray[logAnalyticsRelationCreator].lineRemoteComputerName.toString(); | |
relationName = logAnalyticsArray[logAnalyticsRelationCreator].lineProtocol.concat(": ",logAnalyticsArray[logAnalyticsRelationCreator].lineDestinationPort); | |
processName = logAnalyticsArray[logAnalyticsRelationCreator].lineProcessName.toString(); | |
archiCreateRelationOnModel (relationName,sourceObjName,remoteObjName,processName,importRelationsSubFolder); | |
} | |
console.log(showTime() + ">New relations Created"); | |
/* | |
----------------------------------------------------Totaly done, if you see this happy face, nothing crashed :) ---------------------- | |
*/ | |
var endTime = Date.now(); | |
runningTime = (endTime-startTime)/1000; | |
console.log(showTime() + ">Script done :) It took " + runningTime + " seconds to complete"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment