It is currently May 8th, 2024, 3:56 pm

Custom plugins doesn't update measures

Share and get help with Plugins and Addons
chucky2401
Posts: 2
Joined: November 7th, 2023, 3:35 pm

Custom plugins doesn't update measures

Post by chucky2401 »

Hello,
For my company purpose, I created a plugin to display how many days remaining before our main and administrator account password will expire.

I'm currently learning C#, so I used the example PluginSystemVersion to create mine.
The widget works, I can see the information, and my coworkers too.

Unfortunately, we must refresh the widget to see the measure update.

I put my code, as I'm beginner, maybe I missed something. I plan to optimize the code later as I will increase my knowledge.

I also tried to put DynamicVariables=1 in my skin, but that didn't change the behavior.

By default the widget will update every hour, for testing purpose I tried every minutes.

Code: Select all

using System;
using System.DirectoryServices;
using System.Runtime.InteropServices;
using Rainmeter;

namespace Information
{
    class Measure
    {
        enum MeasureType
        {
            AdmUsername,
            UserName,
            NoExpiration,
            ExpirationDate,
            DaysRemaining,
            AdmAccount,
            ExpirationDateAdm,
            DaysRemainingAdm
        }

        private MeasureType Type = MeasureType.UserName;

        static public implicit operator Measure(IntPtr data)
        {
            return (Measure)GCHandle.FromIntPtr(data).Target;
        }

        private string userName, admUserName;

        private DateTime dateExpiration, dateExpirationAdm;
        private readonly DateTime maintenant = DateTime.Now;

        private double daysRemaining, daysRemainingAdm;
        private bool noExpiration = false, admAccount = false;

        private DirectoryEntry sterimed;
        private DirectorySearcher recherche, rechercheAdm;
        private SearchResult resultat, resultatAdm;


        internal Measure() {
        }

        internal void GetUserInfo()
        {
            userName = Environment.GetEnvironmentVariable("UserName");
            admUserName = $"adm_{userName}";

            sterimed = new DirectoryEntry($"LDAP://domain.lan/ou=Countries,dc=domain,dc=lan");
            recherche = new DirectorySearcher(sterimed)
            {
                Filter = $"(&(objectCategory=user)(objectClass=user)(samAccountName={userName}))"
            };
            recherche.PropertiesToLoad.Add("msDS-UserPasswordExpiryTimeComputed");

            try {
                resultat = recherche.FindOne();

                long value = (long)resultat.Properties["msDS-UserPasswordExpiryTimeComputed"][0];
                if (value == 9223372036854775807) {
                    noExpiration = true;
                }
                dateExpiration = DateTime.FromFileTime(value);
                daysRemaining = (dateExpiration - maintenant).TotalDays;
            } catch (Exception e) {
                daysRemaining=-1.0;
            }

            rechercheAdm = new DirectorySearcher(sterimed)
            {
                Filter = $"(&(objectCategory=user)(objectClass=user)(samAccountName={admUserName}))"
            };
            rechercheAdm.PropertiesToLoad.Add("msDS-UserPasswordExpiryTimeComputed");

            try {
                resultatAdm = rechercheAdm.FindOne();
            } catch (Exception e) {
                daysRemainingAdm = -1.0;
            }

            if (resultatAdm != null) {
                admAccount = true;
                try {
                    long valueAdm = (long)resultatAdm.Properties["msDS-UserPasswordExpiryTimeComputed"][0];
                    dateExpirationAdm = DateTime.FromFileTime(valueAdm);
                    daysRemainingAdm = (dateExpirationAdm - maintenant).TotalDays;
                } catch (Exception e) {
                    daysRemainingAdm = -1.1;
                }
            }
        }

        internal void Reload(Rainmeter.API api, ref double maxValue)
        {
            string type = api.ReadString("Type", "");
            switch (type.ToLowerInvariant()) {
                case "admusername":
                    Type = MeasureType.AdmUsername;
                    break;

                case "username":
                    Type = MeasureType.UserName;
                    break;

                case "expirationdate":
                    Type = MeasureType.ExpirationDate;
                    break;

                case "daysremaining":
                    Type = MeasureType.DaysRemaining;
                    break;

                case "noexpiration":
                    Type = MeasureType.NoExpiration;
                    break;

                case "admaccount":
                    Type = MeasureType.AdmAccount;
                    break;

                case "expirationdateadm":
                    Type = MeasureType.ExpirationDateAdm;
                    break;

                case "daysremainingadm":
                    Type = MeasureType.DaysRemainingAdm;
                    break;

                default:
                    api.Log(API.LogType.Error, "SystemVersion.dll: Type=" + type + " not valid");
                    break;
            }
        }

        internal double Update() {
            GetUserInfo();

            switch (Type)
            {
                case MeasureType.NoExpiration:
                    return noExpiration == true ? 1.0 : 0.0;

                case MeasureType.AdmAccount:
                    return admAccount == true ? 1.0 : 0.0;

                case MeasureType.DaysRemaining:
                    return Math.Round(daysRemaining, 1);

                case MeasureType.DaysRemainingAdm:
                    return admAccount == true ? daysRemainingAdm : 0.0;
            }

            return 0.0;
        }

        internal string GetString() {
            GetUserInfo();

            switch (Type) {
                case MeasureType.AdmUsername:
                    return admUserName;

                case MeasureType.UserName:
                    return userName;

                case MeasureType.ExpirationDate:
                    return dateExpiration.ToString();

                case MeasureType.ExpirationDateAdm:
                    return admAccount == true ? dateExpirationAdm.ToString() : null;
            }

            return null;
        }
    }

    public class Plugin
    {
        static IntPtr StringBuffer = IntPtr.Zero;

        [DllExport]
        public static void Initialize(ref IntPtr data, IntPtr rm) {
            data = GCHandle.ToIntPtr(GCHandle.Alloc(new Measure()));
            Rainmeter.API api = (Rainmeter.API)rm;
        }

        [DllExport]
        public static void Finalize(IntPtr data)
        {
            GCHandle.FromIntPtr(data).Free();

            if (StringBuffer != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(StringBuffer);
                StringBuffer = IntPtr.Zero;
            }
        }

        [DllExport]
        public static void Reload(IntPtr data, IntPtr rm, ref double maxValue) {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            measure.Reload(new Rainmeter.API(rm), ref maxValue);
        }

        [DllExport]
        public static double Update(IntPtr data)
        {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            return measure.Update();
        }

        [DllExport]
        public static IntPtr GetString(IntPtr data)
        {
            Measure measure = (Measure)GCHandle.FromIntPtr(data).Target;
            if (StringBuffer != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(StringBuffer);
                StringBuffer = IntPtr.Zero;
            }

            string stringValue = measure.GetString();
            if (stringValue != null)
            {
                StringBuffer = Marshal.StringToHGlobalUni(stringValue);
            }

            return StringBuffer;
        }
    }
}

User avatar
Brian
Developer
Posts: 2689
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Custom plugins doesn't update measures

Post by Brian »

chucky2401 wrote: November 7th, 2023, 4:16 pm Unfortunately, we must refresh the widget to see the measure update.
By "update", I am going to assume you mean the "times" you are calculating, since the other values (like UserName) aren't going to change.


I might be wrong, but I believe the issue is with your maintenant variable. It is set once when the measure is created (to DateTime.Now), but I never see it updated anywhere. I believe you should be updating that variable to "Now" at some point before using it in GetUserInfo. Otherwise, it will only reference the time when the measure was created, thus your time calculations will always be fixed until the skin is refreshed.

I am no expert in C#, so there could be other issues, but the fact that you getting the correct values at least once, tells me that the above issue is most likely the problem.

Another piece of advice. Do the "work" in the Update function, and use GetString to just retrieve any strings that are set in Update. The reason is, Rainmeter can call GetString several times during the update cycle. It is called every time the string value is needed (like when referencing the measure via section variables, the about dialog, etc.). You can see an example of this in the AdvancedCPU plugin (it's c++, but hopefully it will be easy to follow). That plugin does all its "work" in Update, then GetString just returns a string without re-doing that "work".

-Brian
chucky2401
Posts: 2
Joined: November 7th, 2023, 3:35 pm

Re: Custom plugins doesn't update measures

Post by chucky2401 »

By "update", I am going to assume you mean the "times" you are calculating, since the other values (like UserName) aren't going to change.
You're right. The only variable supposed to be updated is ExpirationDate and ExpirationDateAdm
I might be wrong, but I believe the issue is with your maintenant variable. It is set once when the measure is created (to DateTime.Now), but I never see it updated anywhere. I believe you should be updating that variable to "Now" at some point before using it in GetUserInfo. Otherwise, it will only reference the time when the measure was created, thus your time calculations will always be fixed until the skin is refreshed.
Damn, you're completely right! I add maintenant = DateTime.Now; in the Update() function, and it works.

But, I see something that I don't understand. I created a .rmskin package to install my plugin, it went in the @Vault folder, but Rainmeter loaded the .dll file in the Plugins folder, I don't remember if I copied it manually. I updated the dll file in the @Vault folder instead of the Plugins folder. What is the rules to update my plugin? On my computer I update the file in the Plugins folder and create a .rmskin package to update it on my coworkers' computer? I'm not sure to understand the purpose of the @Vault folder.
Another piece of advice. Do the "work" in the Update function, and use GetString to just retrieve any strings that are set in Update. The reason is, Rainmeter can call GetString several times during the update cycle. It is called every time the string value is needed (like when referencing the measure via section variables, the about dialog, etc.). You can see an example of this in the AdvancedCPU plugin (it's c++, but hopefully it will be easy to follow). That plugin does all its "work" in Update, then GetString just returns a string without re-doing that "work".
I removed the call to my function GetUserInfo() in the GetString() function. Is it enough on my side?
User avatar
Brian
Developer
Posts: 2689
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Custom plugins doesn't update measures

Post by Brian »

chucky2401 wrote: November 8th, 2023, 8:10 am But, I see something that I don't understand. I created a .rmskin package to install my plugin, it went in the @Vault folder, but Rainmeter loaded the .dll file in the Plugins folder, I don't remember if I copied it manually. I updated the dll file in the @Vault folder instead of the Plugins folder. What is the rules to update my plugin? On my computer I update the file in the Plugins folder and create a .rmskin package to update it on my coworkers' computer? I'm not sure to understand the purpose of the @Vault folder.
Installing .rmskin files do 2 things in regards to plugins. First, it copies the correct 32 or 64 plugin to the Plugins(usually located in %AppData%) folder. Second, a copy of both the 32 and 64 versions of the plugin are stored in the @Vault folder. Rainmeter does not load any plugins from the @Vault folder.

The purpose of the @Vault folder is to provide a central place to users to easily store items you may want to create .rmskins with. If your skin uses a plugin downloaded from the web, it's not easy and straightforward to include that plugin in an rmskin that you create later on. The "installed" plugin will only be the 32 or 64 plugin (not both).

More here: https://docs.rainmeter.net/manual/distributing-skins/vault-folder/

So, when you make a plugin, all you need to do is select the correct 32 AND 64 bit versions of your plugin in the proper fields in the .rmskin packager. Once the .rmskin is made, nothing more is needed in your end. All anyone installing your .rmskin needs to do is install the .rmskin. The correct bitness of the plugin will be installed in the Plugins folder (Rainmeter will use this version), and a copy of both plugins (32 and 64) will be placed in the @Vault folder. These can be removed if you really want to, but Rainmeter won't use them.


chucky2401 wrote: November 8th, 2023, 8:10 am I removed the call to my function GetUserInfo() in the GetString() function. Is it enough on my side?
That looks right, since you are calling GetUserInfo from Update. However, I didn't actually try your code and I am relying on just visual inspection of your code.

-Brian