Igzter Posted June 12, 2010 Posted June 12, 2010 (edited) igzters WMI Library (iWMILib)I am submitting this library in the hopes that someone will save some time using this when working with WMI (Windows Management Instrumentation). I created this library when I found myself writing the same code over and over again making management scripts. Enjoy and please comment upon bugs, improvements and well my lousy AutoIT coding standards If people are interested I'll be posting my NTFS permission and support library (which is dependant upon this library) later on.Best regards,IgiWMILib.au3expandcollapse popup; #INDEX# ======================================================================================================================= ; Title .........: igzters WMI Library (iWMILib) ; ; Version .......: 1.2.0 ; ; AutoIt Version : 3.3.6.1 ; ; Language ......: English ; ; Description ...: A collection of functions for interacting with WMI to local and remote computers. ; ; Author(s) .....: Thomas Uhrfelt ; ; Dll ...........: None. ; ; Functions .....: _iWMI_Initialize ; _iWMI_Connect ; _iWMI_Disconnect ; _iWMI_Dispose ; _iWMI_ExecQueryClassSelect ; _iWMI_SetPingDelay ; _iWMI_QueryCorrection (internal) ; _iWMI_ErrorHandler (internal) ; ; Remarks .......: http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx (WQL - SQL for WMI). ; http://www.autoitscript.com/autoit3/files/beta/autoit/COM/ErrorEventTest-WMI.AU3 (AutoIT and COM error handling). ; http://msdn.microsoft.com/en-us/library/aa394585%28v=VS.85%29.aspx (WMI Tasks for Scripts and Applications) - Good resource for ideas on how to use WMI. ; ; If you have problems and getting "RPC Server unavailible on remote computers you might want to try ; issuing the following commands on the computers: ; >netsh firewall set service RemoteAdmin enable ; >netsh firewall add portopening protocol=tcp port=135 name=DCOM_TCP135 ; ; This include is now multiple callers resistant - so even if you have a couple of scripts within the same project ; including and calling it - it'll cope with that. ; ; Also check out : http://www.autoitscript.com/forum/index.php?showtopic=106163 (Active Directory UDF that is extremely useful for me). ; http://www.autoitscript.com/forum/index.php?showtopic=74325 (Event driven TCP UDF that rocks by boat - simple and very useful). ; ; ToDo: .........: None planned. ; ; History........: ; 0000-00-00 * Been sitting on my harddrive for over a year without use. ; 2010-06-12 * Added a COM error handler event for queries that fails (output to console). ; * Renamed _iWMI_ExecQueryClass to _iWMI_ExecQueryClassSelect. ; * Removed _iWMI_ExecQueryClassAssociators (use the raw query for this). ; * Improved commenting and cleanup of code. ; * Implemented ping test on remote computers, including adjustable ping delay. ; * Corrected bugs using SetError on successful actions. ; * Renamed _iWMI_EnableCorrectionEnable to _iWMI_QueryCorrectionEnable(). ; * Fixed bug with checking $_iWMI_oWbemLocator object creation not working as intended. ; ; 2010-06-13 * Lot's of smaller issues fixed (mainly costmetical). ; * Removed the enable and disable correction functions (handled via parameters instead). ; * Rewrote the logic to allow for multiple callers with their own connections. This will allow this library to ; be included and used from multiple sources at the same time within the same project. ; * Added namespace support to _iWMI_Connect (defaults to root/cimv2). ; * Bumped version to 1.2.0 ; ; =============================================================================================================================== #include <array.au3> Global $_iWMI_oWMIService, _ $_iWMI_oWbemLocator, _ $_iWMI_oWMIError, _ ; Error handler for COM objects. $_iWMI_iPingDelay = 1000, _ ; Default ping delay is 1000ms. $_iWMI_aConnections [1], _ ; Array of our WMI connections currently handled (not used yet). $_iWMI_aConnectionsIds [1], _ ; Array of our WMI connection ids currently handled (not used yet). $_iWMI_iConnectionIdCounter = 0, _ ; Incremental counter for our connection ids (not used yet). $_iWMI_bInitialized = false ; Are we initialized and ready to go? Global Enum _ ; Global error codes. $_iWMI_StatusSuccess = 0 , _ ; Operation succeeded. $_iWMI_StatusGeneralFailure , _ ; Operation failed. $_iWMI_StatusWMIIsInitialized , _ ; Attempt to initialize library failed because it was already initialized. $_iWMI_StatusWMINotInitialized , _ ; Attempt to dispose/use library failed because it was not initalized. $_iWMI_StatusNotValidHost = 20, _ ; The hostname supplied was not a valid one. $_iWMI_StatusNotValidHostType , _ ; The host type supplied was not a valid one. $_iWMI_StatusWMIObjectNotCreated = 40, _ ; WMI object creation failed. $_iWMI_StatusWbemObjectNotCreated , _ ; Wbem object creation failed. $_iWMI_StatusHostNotAvailible = 60, _ ; The host could not be reached (failed ping test). $_iWMI_StatusConnectionIdNotFound = 80, _ ; The connection id was not found (non existant). $_iWMI_StatusArrayFailure, _ ; Failed to add to connection or id array. $_iWMI_StatusNoQueryResults = 100 ; Query returned no results. Global Const _ $_iWMI_HOSTLOCAL = 0, _ ; Host is local. $_iWMI_HOSTREMOTE = 1, _ ; Host is remote. $_iWMI_QUERYCLASSEQUALS = 0, _ ; Class query is using equal. $_iWMI_QUERYCLASSLIKE = 1 ; Class query is using like. (% is added automatically) ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_Initialize ; ; Description ...: Intialize the library. ; ; Parameters ....: None. ; ; Return values .: On Success - Returns $_iWMI_StatusSuccess ; On Failure - Returns errorcode and sets @Error ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_Initialize() if ( $_iWMI_bInitialized ) Then ; Are we already initialized? Return SetError($_iWMI_StatusWMIIsInitialized, 0, $_iWMI_StatusWMIIsInitialized) ; Return error if we are (need to check this error in calling function - this is not a major error). EndIf $_iWMI_oWMIError = ObjEvent("AutoIt.Error","_iWMI_ErrorHandler") ; Install custom COM error handler. If IsObj($_iWMI_oWMIError) = False Then ; Check if the creation or error handler worked. $_iWMI_bInitialized = False ; If we fail then we are not really initialized (make sure we need another initialization ) - probably should reset everything here?! Return SetError($_iWMI_StatusWMINotInitialized, 0, $_iWMI_StatusWMINotInitialized) ; and return error! EndIf $_iWMI_iConnectionIdCounter = 0 ; Reset the connection id counter (can be reset whenever connections are null) $_iWMI_bInitialized = True ; Yes now we are initialized. Return $_iWMI_StatusSuccess ; Return success if everything went OK. EndFunc ;==>_iWMI_Initialize ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_iWMI_Connect ; ; Description ...: Intialize the library. ; ; Parameters ....: $sHost - Host to connect to (use localhost for local computer). ; $iHostType - Optional - Type of host (local/remote). ; 0 = (Default) Connect to local computer. ($_iWMI_HOSTLOCAL) ; 1 = Connect to remote computer. ($_iWMI_HOSTREMOTE) ; $sUsername - Optional - Username to connect to remote computers ; "" = (Default) Connect with current credentials. ; $sPassword - Optional - Password to connect to remote computer. ; "" = (Default) Connect with current credentials. ; $sNamespace - Optional - namespace to connection to (default: root\cimv2). ; ; Return values .: On Success - Returns $_iWMI_StatusSuccess ; On Failure - Returns errorcode and sets @Error ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_Connect( $sHost, $iHostType = $_iWMI_HOSTLOCAL, $sUsername="", $sPassword="", $sNamespace = "root\cimv2" ) If $_iWMI_bInitialized = False Then ; Check if we are initialized. Return SetError($_iWMI_StatusWMINotInitialized, 0, $_iWMI_StatusWMINotInitialized) ; Return error if we are not. EndIf If $sHost = "" Then ; Check for valid host if not return error. Return SetError($_iWMI_StatusNotValidHost,0,$_iWMI_StatusNotValidHost) EndIf If Not($iHostType = $_iWMI_HOSTLOCAL Or $iHostType = $_iWMI_HOSTREMOTE ) Then ; Check for valid host type (not needed since we default to localhost is omitted. Return SetError($_iWMI_StatusNotValidHostType,0,$_iWMI_StatusNotValidHostType) EndIf ; We are connecting to our local computer. If($iHostType = $_iWMI_HOSTLOCAL) Then ; Initialize local host. $_iWMI_oWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\.\" & $sNamespace ) ; Impersonate If Not IsObj($_iWMI_oWMIService) Then ; We failed to create the object. Return SetError($_iWMI_StatusWMIObjectNotCreated, 0, $_iWMI_StatusWMIObjectNotCreated ) EndIf ; We are connecting to a remote computer. ElseIf($iHostType = $_iWMI_HOSTREMOTE) Then ; Initialize remote host. If Not(Ping($sHost, $_iWMI_iPingDelay )) Then ; Check if host is availible. Return SetError($_iWMI_StatusHostNotAvailible, 0, $_iWMI_StatusHostNotAvailible) EndIf $_iWMI_oWbemLocator=ObjCreate("WbemScripting.SWbemLocator") If Not IsObj($_iWMI_oWbemLocator) Then Return SetError($_iWMI_StatusWbemObjectNotCreated, 0, $_iWMI_StatusWbemObjectNotCreated) EndIf $_iWMI_oWMIService = $_iWMI_oWbemLocator.ConnectServer ( _ $sHost, _ "root\cimv2", _ $sUsername, _ $sPassword ) If Not IsObj($_iWMI_oWMIService) Then $_iWMI_oWbemLocator = "" ; Dispose Wbem object due to the failure. Return SetError($_iWMI_StatusWMIObjectNotCreated, 0, $_iWMI_StatusWMIObjectNotCreated ) EndIf $_iWMI_oWMIService.Security_.authenticationLevel = 6 ; We are using PktPrivacy. EndIf $_iWMI_iConnectionIdCounter += 1 ; Increase the connection id counter. _ArrayAdd( $_iWMI_aConnections, $_iWMI_oWMIService ) ; Put the WMI Service object in the array. If @Error = True Then ; We have a problem. $_iWMI_iConnectionIdCounter -= 1 Return SetError($_iWMI_StatusArrayFailure, 0, $_iWMI_StatusArrayFailure ) EndIf _ArrayAdd( $_iWMI_aConnectionsIds, $_iWMI_iConnectionIdCounter ) If @Error = True Then ; We had a problem - need to revert the connection as well. _ArrayPop( $_iWMI_aConnections ) $_iWMI_iConnectionIdCounter -= 1 Return SetError($_iWMI_StatusArrayFailure, 0, $_iWMI_StatusArrayFailure ) EndIf Return $_iWMI_iConnectionIdCounter ; Return the id of our connection. EndFunc ; ==>_iWMI_Connect ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_Disconnect ; ; Description ...: Disconnects a connection. ; ; Parameters ....: $iId - Connection id to disconnect. ; ; Return values .: On Success - Returns $_iWMI_StatusSuccess ; On Failure - Returns errorcode and sets @Error ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_Disconnect( $iId ) If $_iWMI_bInitialized = False Then ; Check if we are initialized. Return SetError($_iWMI_StatusWMINotInitialized, 0, $_iWMI_StatusWMINotInitialized) ; Return error if we are not. EndIf $iIndex = _ArraySearch( $_iWMI_aConnectionsIds, $iId ) ; Search for our Id. If @Error = True Then ; We did not find the id - ooops! Return SetError($_iWMI_StatusConnectionIdNotFound, 0, $_iWMI_StatusConnectionIdNotFound) EndIf ; if these fails we should abort - something is very wrong - only reason would be AutoIT bugs - revisited later. _ArrayDelete( $_iWMI_aConnections , $iIndex ) _ArrayDelete( $_iWMI_aConnectionsIds, $iIndex ) If ( Ubound($_iWMI_aConnections ) = 1 ) Then ; We are not back to where we came from. $_iWMI_iConnectionIdCounter = 0 EndIf Return $_iWMI_StatusSuccess EndFunc ; ==>_iWMI_Disconnect ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_Dispose ; ; Description ...: Dispose the library. ; ; Return values .: On Success - Returns $_iWMI_StatusSuccess ; On Failure - Returns errorcode and sets @Error ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_Dispose() If $_iWMI_bInitialized = True Then ; We are initialized so we can dispose ReDim $_iWMI_aConnections [1] ; Reset the arrays just in case to re-claim memory. ReDim $_iWMI_aConnectionsIds [1] $_iWMI_iConnectionIdCounter = 0 ; Reset the connection counter. Return $_iWMI_StatusSuccess Else Return SetError($_iWMI_StatusWMINotInitialized, 0, $_iWMI_StatusWMINotInitialized) EndIf Return SetError($_iWMI_StatusGeneralFailure, 0, $_iWMI_StatusGeneralFailure) EndFunc ;==>_iWMI_Dispose ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_QueryCorrection ; ; Description ...: Performs the actual query correction by replacing \ with \\. ; ; Parameters ....: $sQuery - Query to perfom the correction on. ; ; Return values .: $sQuery - The corrected query. ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_QueryCorrection($sQuery) return StringReplace($sQuery,"\","\\") EndFunc ;==>_iWMI_QueryCorrection ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_SetPingDelay ; ; Description ...: Sets the ping timeout for remote connections. ; ; Parameters ....: $iDelay - Optional - Number of miliseconds for ping timeout (default: 1000 ). Min value is 1000ms. ; ; Return values .: On Success - Returns $_iWMI_StatusSuccess. ; On Failure - Returns errorcode and sets @Error. ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_SetPingDelay( $iDelay = 1000 ) If $iDelay < 1000 Then Return SetError($_iWMI_StatusGeneralFailure, 0, $_iWMI_StatusGeneralFailure) EndIf $_iWMI_iPingDelay = $iDelay Return $_iWMI_StatusSuccess EndFunc ;==> _iWMI_SetPingDelay ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_ErrorHandler ; ; Description ...: COM event error handler. Handles COM errors frequently thrown by erroneous WQL statements (or some statements ; with NULL returns). ; ; Parameters ....: None. ; ; Return values .: None. ; ; Remarks .......: In the future I probably need to improve the error handling with return values. ; ; Author(s)......: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_ErrorHandler() ConsoleWrite("Error.number: " & Hex( $_iWMI_oWMIError,8) & @CRLF ) ConsoleWrite("Error.windescription: " & $_iWMI_oWMIError.windescription & @CRLF ) ConsoleWrite("Error.source: " & $_iWMI_oWMIError.source & @CRLF ) ConsoleWrite("Error.helpfile: " & $_iWMI_oWMIError.helpfile & @CRLF ) ConsoleWrite("Error.helpcontext: " & $_iWMI_oWMIError.helpcontext & @CRLF ) ConsoleWrite("Error.lastdllerror: " & $_iWMI_oWMIError.lastdllerror & @CRLF ) ConsoleWrite("Error.scriptline: " & $_iWMI_oWMIError.scriptline & @CRLF ) Endfunc ;==>_iWMI_ErrorHandler ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_ExecQueryClassSelect ; ; Description ...: Executes a WMI class query on the connected computer. ; ; Parameters ....: $iId - Id of the connection. ; $sClass - Class to query. ; $bCorrection - Optional - Use query correction. ; 0 = (Default) Do not use query correction. ; 1 = Use query correction. ; $sQueryType - Optional - Equals or Like query. ; 0 = (Default) Use an equal query ($_iWMI_QUERYCLASSEQUALS) ; 1 = Use a like query ($_iWMI_QUERYCLASSLIKE) ; $sProperty - Optional - Which property to select on (default: ""). ; $sPropertyValue - Optional - Which property value to select on (default: ""). ; $sProperties - Optional - Which properties to query (default: "*" ). ; $iFlag - Optional - Flag to execute the query (default: 0x00). ; ; Return values .: On Success - Returns collection of results from the query. ; On Failure - Returns errorcode and sets @Error ; ; Remarks .......: When querying for files/folders with spaces in them please be careful how you use ' & ", example: ; _iWMI_ExecQueryClass( "Win32_LogicalFileSecuritySetting", "Path", '"C:\\Autoexec.bat"' ) unless ; you have query correction enabled. ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_ExecQueryClassSelect( $iID, $sClass, $bCorrection = False, $sQueryType = $_iWMI_QUERYCLASSEQUALS, $sProperty = "", $sPropertyValue = "", $sProperties = "*", $iFlag = 0x00 ) If $_iWMI_bInitialized = False Then ; Check if we are initialized. Return SetError($_iWMI_StatusWMINotInitialized, 0, $_iWMI_StatusWMINotInitialized) ; Return error if we are not. EndIf $iIndex = _ArraySearch( $_iWMI_aConnectionsIds, $iId ) ; Search for our Id. If @Error = True Then ; We did not find the id - ooops! Return SetError($_iWMI_StatusConnectionIdNotFound, 0, $_iWMI_StatusConnectionIdNotFound) EndIf If $sProperties = "" Then $sProperties = "*" ; Create the query Local $sQuery = "SELECT " & _ $sProperties & _ " FROM " & _ $sClass If $sProperty <> "" And $sPropertyValue <> "" And $sQueryType = $_iWMI_QUERYCLASSEQUALS Then ; Equals query. $sQuery = $sQuery & _ " WHERE " & _ $sProperty & _ '="' & _ $sPropertyValue & '"' ElseIf $sProperty <> "" And $sPropertyValue <> "" And $sQueryType = $_iWMI_QUERYCLASSLIKE Then ; Like query. $sQuery = $sQuery & _ " WHERE " & _ $sProperty & _ ' LIKE %"' & _ $sPropertyValue & '%"' EndIf If $bCorrection = True Then ; Should we correct the query $sQuery = _iWMI_QueryCorrection( $sQuery ) EndIf $iIndex = _ArraySearch( $_iWMI_aConnectionsIds, $iId ) ; Search for our Id. If @Error = True Then ; We did not find the id - ooops! Return SetError($_iWMI_StatusConnectionIdNotFound, 0, $_iWMI_StatusConnectionIdNotFound) EndIf Local $cResults = $_iWMI_aConnections[$iIndex].ExecQuery( $sQuery, "WQL", $iFlag ) ; Execute the query if we found the connection. If ($cResults.Count = 0) Then ; Did we get any results? Return SetError($_iWMI_StatusNoQueryResults, 0, $_iWMI_StatusNoQueryResults) ; No - return an error. EndIf Return $cResults EndFunc ;==>_iWMI_ExecQueryClassSelect ;#FUNCTION# ==================================================================================================================== ; Name...........: _iWMI_ExecQueryRaw ; ; Description ...: Executes a raw WMI class query on the connected computer. ; ; Parameters ....: $iId - Id of the connection. ; Parameters ....: $sQuery - Query string. ; $bCorrection - Optional - Use query correction. ; 0 = (Default) Do not use query correction. ; 1 = Use query correction. ; $iFlag - Optional - Flag to execute the query (default: 0x00). ; ; Return values .: On Success - Returns collection of results from the query. ; On Failure - Returns errorcode and sets @Error ; ; Author(s) .....: Thomas Uhrfelt ; =============================================================================================================================== Func _iWMI_ExecQueryRaw( $iId,$sQuery,$bCorrection = False,$iFlag = 0x00 ) If $_iWMI_bInitialized = False Then ; Check if we are initialized. Return SetError($_iWMI_StatusWMINotInitialized, 0, $_iWMI_StatusWMINotInitialized) ; Return error if we are not. EndIf $iIndex = _ArraySearch( $_iWMI_aConnectionsIds, $iId ) ; Search for our Id. If @Error = True Then ; We did not find the id - ooops! Return SetError($_iWMI_StatusConnectionIdNotFound, 0, $_iWMI_StatusConnectionIdNotFound) EndIf If $bCorrection = True Then ; Should we correct the query $sQuery = _iWMI_QueryCorrection( $sQuery ) EndIf Local $cResults = $_iWMI_aConnections[$iIndex].ExecQuery( $sQuery, "WQL", $iFlag ) ; Execute the query if we found the connection. If ($cResults.Count = 0) Then ; Did we get any results? Return SetError($_iWMI_StatusNoQueryResults, 0, $_iWMI_StatusNoQueryResults) ; No - return an error. EndIf Return $cResults EndFunc ;==>_iWMI_ExecQueryRawiWMILibTest.au3expandcollapse popup#NoTrayIcon #include <iWMILib.au3> ; No error checking is done in the example! _iWMI_Initialize() $myid1 = _iWMI_Connect("localhost") ; Connection one $myid2 = _iWMI_Connect("192.168.231.103", $_iWMI_HOSTREMOTE, "username", "password" ) ; Connection 2 ConsoleWrite( "Querying logical disks on remote computer ($myid2)" & @CRLF ) $Files = _iWMI_ExecQueryClassSelect( $myid2, "Win32_LogicalDisk", True ) For $oFile In $Files ConsoleWrite( $oFile.Name & @CRLF ) Next ConsoleWrite( "Querying logical disks on local computer ($myid1)" & @CRLF ) $Files = _iWMI_ExecQueryClassSelect( $myid1, "Win32_LogicalDisk", True ) For $oFile In $Files ConsoleWrite( $oFile.Name & @CRLF ) Next ConsoleWrite( "Querying (raw) for all subfolders in C:\Windows on local computer ($myid1)" & @CRLF ) $Files = _iWMI_ExecQueryRaw ( $myid1, 'ASSOCIATORS OF {Win32_Directory.Name="C:\WINDOWS"} WHERE AssocClass=Win32_SubDirectory ResultRole=PartComponent', True ) For $oFile In $Files ConsoleWrite( $oFile.Name & @CRLF ) Next ConsoleWrite( "Querying (raw) for all files in C:\ on remote computer ($myid2)" & @CRLF ) $Files = _iWMI_ExecQueryRaw ( $myid2, 'ASSOCIATORS OF {Win32_Directory.Name="C:\"} WHERE ResultClass = CIM_DataFile', True ) For $oFile In $Files ConsoleWrite( $oFile.Name & @CRLF ) Next _iWMI_Disconnect($myid1) _iWMI_Disconnect($myid2) _iWMI_Dispose() Edited June 13, 2010 by Igzter
Igzter Posted June 13, 2010 Author Posted June 13, 2010 Updated to 1.1.0 * Lot's of smaller issues fixed (mainly costmetical). * Removed the enable and disable correction functions (handled via parameters instead). * Rewrote the logic to allow for multiple callers with their own connections. This will allow this library to be included and used from multiple sources at the same time within the same project. Ig
Igzter Posted June 13, 2010 Author Posted June 13, 2010 Updated to 1.2.0 * Added namespace support to _iWMI_Connect (defaults to root/cimv2). * Bumped version to 1.2.0
UEZ Posted June 13, 2010 Posted June 13, 2010 I'm using WMI, too and your WMI approach is very interessting! Thanks for sharing! BR, UEZ Please don't send me any personal message and ask for support! I will not reply! Selection of finest graphical examples at Codepen.io The own fart smells best! ✌Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!¯\_(ツ)_/¯ ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ
Igzter Posted June 13, 2010 Author Posted June 13, 2010 I'm using WMI, too and your WMI approach is very interessting!Thanks for sharing!BR,UEZThe reason for my approach of having a multi-entrant UDF is that I use this from many other UDFs I made and sometimes those UDFs is in the same project - so I have no idea on how many times it is getting used before hand.Ig
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now