#RequireAdmin #Region ;**** Directives created by AutoIt3Wrapper_GUI **** #AutoIt3Wrapper_Icon=Google-plus.ico #AutoIt3Wrapper_UseX64=y #AutoIt3Wrapper_Res_Comment=Learning Tree Web Links #AutoIt3Wrapper_Res_Description=Create URL links from data in Userdata fields #AutoIt3Wrapper_Res_Fileversion=4.1.0.0 #AutoIt3Wrapper_Res_LegalCopyright=Learning Tree International #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI **** #include #include #include #include #include #include ;Developed by Robin Johnston 2020 ;V1.8 - Added support for individual URLs ;V2.0 - Added custom option that can add all sorts of URLs ;V3.0 - Updated to support sourcing data from Env Data ;V3.1 - Added Mural Links support ;V4.0 - Migrated to Cloudshare ;V4.0.2 - Fixed a glitch re detecting Mural ;V4.0.4 - Fixed the Individual URL detection and won't call real API unless it has to ;V4.1.0 - All good and added Powershell option Global $force = False Global $silent = False Global $LogFile = "WebLinks.log" FileDelete(@ScriptDir & '\' & $LogFile) If StringInStr($cmdlineraw, "/f") Or Not @Compiled Then ;allow a forced update RegDelete("HKLM\Software\WebLinks","Class") RegDelete("HKLM\Software\WebLinks","MD5") $force = True EndIf Global $hLogFile = FileOpen(@ScriptDir & '\' & $LogFile, 1) WriteLog("", 1) If $force Then WriteLog("Forced restart with /f switch, log cleared, registry emptied") Global $userdata="" ;Only run once If Not _Singleton(@ScriptName) Then WriteLog("Already running.") Exit EndIf If StringInStr($cmdlineraw, "/s") Then $silent = True WriteLog("Silent mode enabled via /s switch") Else ProgressOn("Web Links Info V4.1","Web Links deployment progress","Awaiting Info from Cloudshare metadata") EndIf ;Pull initial metadata from CS $backoff=1000+Random(1,1000,1) Do $metadata=BinaryToString(InetRead("https://metadata.cloudshare.com/api/v3/unauthenticated/metadata")) WriteLog("CS Metadata: " & Json_Encode($metadata)) ;Note Metadata always starts with lowercase letter - should change this is backend! Local $oJSON = Json_Decode($metadata) If Not @error Then ; Access the nested object Local $oCustom = Json_ObjGet($oJSON, "custom-properties") WriteLog("CS Custom-Properties: " & Json_Encode($oCustom)) ; Example: read a specific property $class=Json_ObjGet($oJSON,"class-id") Else ;Metadata could not be read, backoff Sleep($backoff) $backoff = $backoff * 2 EndIf If $backoff > 64000 Then ;Have we been here before? If RegRead("HKLM\Software\WebLinks","Backoff") == "ONCE" Then ;Already done this once - so throw up error and quit RegDelete("HKLM\Software\WebLinks","Backoff") WriteLog("Quitting because no metadata discovered") MsgBox(0,"Web Links Error","Unable to pull Cloudshare metadata" & @CR & @CR & "Check URL below then reboot and try again" & @CR & "https://metadata.cloudshare.com/api/v3/unauthenticated/metadata/class-id") Exit Else RegWrite("HKLM\Software\WebLinks","Backoff", "REG_SZ", "ONCE") WriteLog("Rebooting to see if we can pickup metadata on subsequent boot") Shutdown(6) ;Reboot if taking too long EndIf EndIf Until Not @error ;hopefully got some metadata WriteLog("Cloudshare ClassId from metadata: " & $class) ;ConsoleWrite("Cloudshare ClassId from metadata: " & $class & @CR) If Not StringInStr($class, "CO") Then WriteLog("No valid Cloudshare ClassId detected in metadata, Exiting...") Exit EndIf ;Compare classId to saved one in registry, if a match, exit (as in already run) If RegRead("HKLM\Software\WebLinks","Class") == $class And @Compiled Then ;It's a match so nothing more to do WriteLog("ClassId matched in registry, all seems OK, so exiting") Exit EndIf ;Need to decide if Instructor or Student VM Global $Instructor = False If StringInStr(Json_ObjGet($oJSON, "env-name"),"instructor") Then $instructor = True ;look for instructor params in metadata, if missing get them from API If Json_ObjGet($oCustom, "studNum")=Null Then ;ConsoleWrite("StudNum is: Null" & @CRLF) ;Need to try pulling UserData from description field on real API ;Have the class_id, use real api to pull the experience_id from v3 api $training = _CS_V3_Get("/class/" & $class) $userdata = _CS_V4_Get("/trainings?name=contains(" & $training & ")") ;V3 & V4 Get just returns required fields here, namely training name and training description WriteLog("Cloudshare Training name from V3 API is " & $training) WriteLog("Cloudshare userdata from V4 API is " & $userdata) EndIf EndIf WriteLog("Instructor VM: " & $Instructor) ;_Crypt_Startup() ; To optimize performance start the crypt library. ;$hash = _Crypt_HashData($userdata, $CALC_MD5) ; Create a hash of the text entered. ;_Crypt_Shutdown() ;ConsoleWrite("MD5 Hash: " & $hash) ;Little extra bit to support Powershell commands If Not $silent Then ProgressSet(10,"Looking for a Powershell command") Sleep(1000) WriteLog("Looking for Powershell command - processing now...") ;Check if Powershell field defined.. If $oCustom.Exists("powershell") Then ;We have a Powershell command, lets run it - everything else gets ditched WriteLog("Possible Powershell command, checking") $powerShell = Json_ObjGet($oCustom, "powershell") If ($powerShell=Null Or $powerShell="") And $Instructor Then ;No Powershell info in metadata, check in Real API for instructor only $powerShell=GetCSParam($userdata,"Powershell:") EndIf If $powerShell="" Or @error Then WriteLog("No valid Powershell script found, skipping execution") Else ProgressSet(25,"Discovered a Powershell command, executing now") ConsoleWrite("Found Powershell: " & $powerShell & @CRLF) ; Run a PowerShell one-liner from AutoIt If @Compiled Then Local $sCmd = 'powershell -NoProfile -ExecutionPolicy Bypass -Command ' & $powerShell Else Local $sCmd = 'powershell -NoProfile -ExecutionPolicy Bypass -NoExit -Command ' & $powerShell EndIf Run($sCmd, "", @SW_SHOW) ;MsgBox(0,"","Test") RegWrite("HKLM\Software\WebLinks","Class", "REG_SZ", $class) Exit EndIf Else WriteLog("No Powershell command found") EndIf ;If we are running, delete any existing links first FileDelete(@DesktopDir & "\\*.url") ;NEW APPROACH - pull info from environment user data fields so when shared drive goes away... If Not $silent Then ProgressSet(30,"Looking for Group URLs") Sleep(2000) WriteLog("Looking for group URLs - processing now...") ;Check if Group 1 link defined. If not no groups to progess If $oCustom.Exists("group 1 Link") Then ;We have group links $link=1 Do $gUrl=Json_ObjGet($oCustom, "group " & $link & " Link") If ($gUrl=Null Or $gUrl="") And $Instructor Then ;No Group info in metadata, check in Real API for instructor only $gUrl=GetCSParam($userdata,"G" & $link & ":") ;Also check for Mural style link If @error Then $gUrl=GetCSParam($userdata,"M" & $link & ":") EndIf If $gUrl="" Or @error Then WriteLog("No more group links found, exiting creation loop") ExitLoop EndIf ;MsgBox(0,"Link " & $link,$gUrl) ;ConsoleWrite("Found Group URL: "&$gUrl & @CRLF) If Not $silent Then ProgressSet(40,"Creating Group URLs") CreateInternetShortcut("GROUP " & $link, $gUrl, @ScriptDir & "\Group.ico") WriteLog("Created group " & $link & " link to " & $gUrl) $link += 1 Sleep(500) Until $link>20 If FileExists(@ScriptDir & "\ReIcon_x64.exe") And $link>1 Then If Not $silent Then ProgressSet(50,"Repositioning group icons") Sleep(2000) Run(@ScriptDir & "\ReIcon_x64.exe /R /ID rob",@ScriptDir & "\") WriteLog("Group icon positions have been restored") EndIf Else ProgressSet(40,"No Group links discovered") WriteLog("No Group links discovered") EndIf ProgressSet(50,"Looking for custom URLs") Sleep(2000) WriteLog("Looking for custom URLs - processing now...") ;Check if URL 1 link defined. If not no custom groups to process If $oCustom.Exists("url 1 Link") Then $link=1 Do $gUrl=Json_ObjGet($oCustom, "url " & $link & " Link") If ($gUrl=Null Or $gUrl="") And $Instructor Then ;No Custom url in metadata, check in Real API for instructor only $gUrl=GetCSParam($userdata,"URL " & $link & " Link:") EndIf If $gUrl="" Or @error Then WriteLog("No more custom links found, exiting creation loop") ExitLoop EndIf ;MsgBox(0,"URL Link " & $link,$gUrl) ConsoleWrite("Found custom URL: "&$gUrl & @CRLF) $name="Custom " & $link $newname=Json_ObjGet($oCustom, "url " & $link & " Name") If ($newname=Null Or $newname="") And $Instructor Then ;No Custom name in metadata, check in Real API for instructor only $newname=GetCSParam($userdata,"URL " & $link & " Name:") EndIf If Not @error And StringLen($newname)>0 Then $name=$newname EndIf ProgressSet(85,"Creating the custom URLs") CreateInternetShortcut($name, $gUrl, @ScriptDir & "\Custom.ico") WriteLog("Created "& $name & " link to " & $gUrl) Sleep(2000) $link += 1 Sleep(500) Until $link>3 Else ProgressSet(60,"No custom URLs discovered") WriteLog("No custom URLs discovered") EndIf ProgressSet(70,"Looking for individual URLs") Sleep(2000) WriteLog("Looking for individual URLs - processing now...") ;Check if Individual 1 link defined. If not no custom groups to process If $oCustom.Exists("individual 1 Link") Then ;Different approach here - which machine are we and grab correct link $link=Json_ObjGet($oCustom, "studNum") $gUrl="" ;0/null is instructor, 1 upwards is student If $link=Null And $Instructor Then ;legacy instructor, pull from API $gUrl=GetCSParam($userdata,"Instructor Link:") ConsoleWrite("Found Instructor URL: " & $gUrl & @CRLF) $name="Instructor URL" $newname=GetCSParam($userdata,"Individual URL Name:") If Not @error And StringLen($newname)>0 Then $name=$newname EndIf ElseIf $link=0 And $Instructor Then ;metadata instructor info so use it $gUrl=Json_ObjGet($oCustom, "instructor Link") If $gUrl=Null Or $gUrl="" Then ;No Individual url in metadata, not available for students in real API WriteLog("No Instrucror link found in metadata") Else ConsoleWrite("Found Instructor URL: " & $gUrl & @CRLF) $name="Instructor URL" $newname=Json_ObjGet($oCustom, "individual URL Name") If Not @error And StringLen($newname)>0 Then $name=$newname EndIf EndIf Else ;assuming a student link and only in metadata $gUrl=Json_ObjGet($oCustom, "individual " & $link & " Link") If $gUrl=Null Or $gUrl="" Then ;No Individual url in metadata, not available for students in real API WriteLog("No individual link found for VM " & $link) Else ;MsgBox(0,"URL Link " & $link,$gUrl) ConsoleWrite("Found individual URL: " & $gUrl & @CRLF) $name="Individual " & $link $newname=Json_ObjGet($oCustom, "individual URL Name") If Not @error And StringLen($newname)>0 Then $name=$newname EndIf EndIf EndIf If StringLen($gUrl)>5 Then ProgressSet(75,"Creating the Individual URL") CreateInternetShortcut($name, $gUrl, @ScriptDir & "\Individual.ico") WriteLog("Created "& $name & " link to " & $gUrl) Sleep(2000) $link += 1 Sleep(500) Else WriteLog("Something went wrong or non existant individual URL") EndIf Else ProgressSet(80,"No individual URLs discovered") WriteLog("No individual URLs discovered") EndIf ;Drop userdata MD5 into registry to prevent rerun ;RegWrite("HKLM\Software\WebLinks","MD5","REG_SZ",$hash) RegWrite("HKLM\Software\WebLinks","ClassID", "REG_SZ", $class) If Not $silent Then ProgressSet(100,"Web Links deployment completed. Exiting...") Sleep(10000) ProgressOff() EndIf WriteLog("Web Links deployment completed. Exiting...") Exit ; ====================================================================================== ; FUNCTIONS USED ; ====================================================================================== Func GetCSParam($sData,$sKey) ;ConsoleWrite("Looking for " & $sKey & " in " & $sData & @CRLF) If StringInStr($sData,$sKey) Then ; Trim leading/trailing whitespace around the whole string $sData = StringStripWS($sData, 3) ; Regex tolerant of spaces around colon/value/comma ;Local $sPattern = $sKey & "\s*([^,\s]+)" Local $sPattern = $sKey & "\s*([^,]+)" ; Run regex search Local $aMatch = StringRegExp($sData, $sPattern, 1) ; If found, return the captured value, else return empty If @error Or UBound($aMatch) = 0 Then Return SetError(1,0,"") ; nothing found Else ;MsgBox(0,"Initial Result for " & $sKey,'"'&$aMatch[0]&'"') $data=StringStripWS($aMatch[0],1) If Not @Compiled Then ProgressOff() ;MsgBox(0,"Initial Result for " & $param,'"'&$data&'"') If StringInStr($data,',') Then $data=StringLeft($data,StringInStr($data,',')-1) ;MsgBox(0,"Cut Result for " & $param,$data) ;When no http, link has been truncated, rebuild on instructor machine only now If Not StringInStr($data,'http') Then ;MsgBox(0,"Inside no HTTP bit" ,"---") If StringRegExp($sKey, "G\d:") Then ;MsgBox(0,"Inside Google Link bit" ,"---") ;assume its a google drive URL so we need to build it out $data="https://drive.google.com/drive/folders/" & $data & "?hl=en" ElseIf StringRegExp($sKey, "M\d:") Then ;MsgBox(0,"Inside Mural link bit" ,"---") ;assume its a Mural URL so we need to build it out $data="https://app.mural.co/t/learningtree9268/m/learningtree9268/" & $data ;https://app.mural.co/t/learningtree9268/m/learningtree9268/1726059441585/349b6b9fa7ff54bb6c5b888ab8f2f0b977754915?sender=u03d1986c42f558f4eb324177 EndIf Else If StringInStr($data,@LF) Then ;trim to right from space ;MsgBox(0,"",StringInStr($data,@LF)) $data=StringLeft($data,StringInStr($data,@LF)-1) EndIf EndIf ;MsgBox(0,"Final Result for " & $param,$data) Return($data) EndIf Else Return SetError(1,0,"") EndIf EndFunc ;==>GetCSParam ;Create a URL shortcut - optional icon file called group.ico Func _URLCreateShortcut($sUrl, $sName, $icon="", $sFldr = @DesktopDir) ;ConsoleWrite("Created " & $sName & " link to " & $sUrl & @CR) $sFldr = StringRegExpReplace($sFldr, "\\+\z", "") & "\" If NOT FileExists($sFldr) Then Return SetError(1) $sName = StringReplace($sName, ".url", "") & ".url" Local $Ini = $sFldr & $sName, $iSec = "InternetShortcut" $sUrl = StringRegExpReplace($sUrl, "^.*?:/+", "") IniWrite($Ini, $iSec, "URL", $sURL) IniWrite($Ini, $iSec, "IDList", "") If $icon <> "" And FileExists($icon) Then IniWrite($Ini, $iSec, "IconFile", $icon) Else IniWrite($Ini, $iSec, "IconFile", "") EndIf IniWrite($Ini, $iSec, "IconIndex", "1") EndFunc ;==>_URLCreateShortcut ;alternative way of doing the above Func CreateInternetShortcut($sName, $sUrl, $icon = "") ;ConsoleWrite("Created " & $sName & " link to " & $sUrl & @CR) Local $shortcutPath = @DesktopDir & "\" & $sName & ".url" Local $content = "[InternetShortcut]" & @CRLF & _ "URL=" & $sUrl & @CRLF ; Optional icon If $icon <> "" And FileExists($icon) Then ;ConsoleWrite($icon & @CR) $content &= "IconFile=" & $icon & @CRLF & "IconIndex=0" & @CRLF EndIf ; Write the file FileDelete($shortcutPath) ; Overwrite if exists FileWrite($shortcutPath, $content) EndFunc