0

I have a ruby script that basically checks for few keys in registry that are flags for our application. The script would check if the flag was 'on'(DWORD value 1) for longer than 14 days, it will turn it off(0) and send out an email.

To track this, what the script does is during the first run, it creates a local DB and store the flag name, date time and so on of every flag that is on in the registry. During next run, it checks this DB to find if the flag has been on for more than 14 days.

I wanted to know if there is a better way to handle this using powershell or should i create a local DB and follow the same flow?

Further info:

So i will have different accounts under '\HKEY_LOCAL_MACHINE\SOFTWARE\Company\Clients' and each client will have a debug folder which will have the flags as seen below

HKEY_LOCAL_MACHINE\SOFTWARE\Company\Clients
                                        \Client1\Debug
                                                    IsEmail
                                                    IsShip
                                        \Client2\Debug
                                                    IsEmail
                                                    IsPack

So i need to effectively iterate through each client, maybe create an XML/JSON or DB with the client name, flags defined and current date time. Then during a later run, i should be accessing this info, compare the registry entries and turn off flags that have been turned for longer than a days specified. I will use the timestamp saved early for this.

Update 1:

Few updates based on my updated understanding of how powershell scripts work. I will have a master array or sort with a list of flags i need. For examples $MasterFlagList=['IsEmail','IsShip','IsNew']. Now during my first run of the application, i would like to create a json string like @tukan mentioned in this answer or xml file that will be something like:

<xml>
<Client1>
    <IsEmail>
        <value>1</value>
        <time>*current time*</time>
    </IsEmail>
    <IsShip>
        <value>1</value>
        <time>*current time*</time>
    </IsShip>
    <IsPack>
        <value>0</value>
        <time>*current time*</time>
    </IsPack>
</Client1>
<Client2>
    <IsEmail>
        <value>0</value>
        <time>*current time*</time>
    </IsEmail>
    <IsShip>
        <value>0</value>
        <time>*current time*</time>
    </IsShip>
    <IsPack>
        <value>0</value>
        <time>*current time*</time>
    </IsPack>
</Client2>
</xml>

So basically what happens here is that the XML string will have all the flags from masterlist, but if there is an entry for those in registry, that value will be loaded(0 or 1) and for those not mentioned in registry, default value of 0 will be set.

Next time i run, i will load this XML from directory into an object and for each client, i need to check if the XML already has a record, if so check the date time and see if the flag has been on for an amount of time, if so turn the flag off.

A better solution to implement the idea is also welcome. I am new to powershell and that why i thought of this approach.

Aswin Francis
  • 87
  • 1
  • 13

1 Answers1

1

That is really a nasty hack. Why don't you store the date also in the registry with the client record?

I think Powershell does better job in querying registry than ruby.

To get values from a registry path:

Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug

This yields result:

IsEmail      : 0
IsShip       : 0
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1
PSChildName  : Debug
PSProvider   : Microsoft.PowerShell.Core\Registry

Alternative is to do it this way:

Get-Item -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug

Which gives:

    Hive: HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1


Name                           Property
----                           --------
Debug                          IsEmail : 0
                               IsShip  : 0

If you want get the value directly, you have to do it this way:

(Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug -Name IsEmail).IsEmail

To set a value (don't forget that you probably need admin rights for that!):

Set-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\company\Clients\Client1\Debug -Name 'IsEmail' -Type Dword -Value '1'

Now to the core of your question (and probably where you have problems)

I had to test it as i had different solution on my mind so it took quite a while.

# %Y ... year
# %m ... month
# %d ... day
# %R ... 24 hour time and minutes
# %S ... seconds
# example output: 20180226_10:38:23
$time_stamp= Get-Date -UFormat "%Y%m%d_%R:%S"
Write-Output $time_stamp

Get-ChildItem 'HKLM:\SOFTWARE\company\Clients' -Recurse | ForEach-Object {
    $regkey = (Get-ItemProperty $_.pspath)

    $regkey.PSObject.Properties | ForEach-Object {
      # you could filter it via -> If($_.Name -like 'Is*'){
        # $regkey is a HashTable
        # Printing some output so you can check
        Write-Output $regkey.PSParentPath
        Write-Output $regkey.PSPath
        Write-Output $_.Name ' = ' $_.Value
        Write-Output ''
        # convert to JSON - will contain more information than you need -> format it as you wish
        $convert_registry_to_json = $regkey | ConvertTo-Json
      #}
    }
}

# Printing JSON output
Write-Output $convert_registry_to_json

Any further filtering or formatting I leave to the discretion.

Edit - edited question This made me remember why I hate XML :). I have create a simple solution to your formatting. This code just a proof of concept, I recommend using functions to shorten your code (also avoiding duplicity)

$time_stamp= Get-Date -UFormat "%Y%m%d_%R:%S"

$result_hash = @{}
$result_array= @()
$first_level_hash = @{}
$second_level_hash = @{}

Get-ChildItem 'HKLM:\SOFTWARE\company\Clients' -Recurse | ForEach-Object {
    $regkey = (Get-ItemProperty $_.pspath)

    $regkey.PSObject.Properties | ForEach-Object {
        # a HashTable
        #Write-host $regkey.PSParentPath
        $parent_key = $regkey.PSParentPath -Match '\w+$'
        #Write-host "Found Parent:" $matches[0]
        $parent_value = $matches[0]
        #Write-Host $regkey.PSPath

        #Write-Host $_.Name ' = ' $_.Value
        #Write-host ''
        $hash_key = $_.Name
        $hash_value = $_.Value

        If ($hash_key -like 'IsEmail'){
            $second_level_hash.Add('value',$hash_value)
            $second_level_hash.Add('time',$time_stamp)
            $first_level_hash.Add($hash_key,$second_level_hash)
            $result_hash.Add($parent_value,$first_level_hash)
            $result_array += ($result_hash)

            $second_level_hash=@{}
            $first_level_hash=@{}
            $result_hash=@{}
        } ElseIf ($hash_key -like 'IsShip'){
            $second_level_hash.Add('value',$hash_value)
            $second_level_hash.Add('time',$time_stamp)
            $first_level_hash.Add($hash_key,$second_level_hash)
            $result_hash.Add($parent_value,$first_level_hash)
            $result_array += ($result_hash)

            $second_level_hash=@{}
            $first_level_hash=@{}
            $result_hash=@{}

        } ElseIf ($hash_key -like 'IsPack'){
            $second_level_hash.Add('value',$hash_value)
            $second_level_hash.Add('time',$time_stamp)
            $first_level_hash.Add($hash_key,$second_level_hash)
            $result_hash.Add($parent_value,$first_level_hash)
            $result_array += ($result_hash)

            $second_level_hash=@{}
            $first_level_hash=@{}
            $result_hash=@{}

        }
        #$convert_registry_to_json = $regkey | ConvertTo-Json
      #}
    }
}
#Write-Output $result_hash
#Write-Output $result_hash.Item('IsEmail').Keys
#Write-Output $result_hash.Item('Client1').item('IsEmail')

Write-Output $result_array

ForEach ($result in $result_array) {
    ForEach ($entry in $result.GetEnumerator()) {
        $first_level_name = $($entry.Value)
        ForEach ($second_level in $first_level_name.GetEnumerator()) {
            $second_level_name = $($second_level.Value)
            ForEach ($third_level in $second_level_name.GetEnumerator()) {
                Write-Host "$($entry.Name) -> $($second_level.Name) -> $($third_level.Name): $($third_level.Value)"
            }
        }
    }
}

# You can convert it to JSON
# FSpecifies how many levels of contained objects are included in the JSON representation. The default value is 2.
$convert_result_to_json = $result_array | ConvertTo-Json -Depth 3

Write-Output $convert_result_to_json

The output now is, which can be easily converted to JSON or XML if you want :

Now it yields result:

Name                           Value
----                           -----
Client1                        {IsEmail}
Client1                        {IsShip}
Client2                        {IsEmail}
Client2                        {IsPack}
Client1 -> IsEmail -> time: 20180226_13:31:58
Client1 -> IsEmail -> value: 1
Client1 -> IsShip -> time: 20180226_13:31:58
Client1 -> IsShip -> value: 0
Client2 -> IsEmail -> time: 20180226_13:31:58
Client2 -> IsEmail -> value: 1
Client2 -> IsPack -> time: 20180226_13:31:58
Client2 -> IsPack -> value: 0

with JSON format:

[
    {
        "Client1":  {
                        "IsEmail":  {
                                        "time":  "20180226_14:40:02",
                                        "value":  1
                                    }
                    }
    },
    {
        "Client1":  {
                        "IsShip":  {
                                       "time":  "20180226_14:40:02",
                                       "value":  0
                                   }
                    }
    },
    {
        "Client2":  {
                        "IsEmail":  {
                                        "time":  "20180226_14:40:02",
                                        "value":  1
                                    }
                    }
    },
    {
        "Client2":  {
                        "IsPack":  {
                                       "time":  "20180226_14:40:02",
                                       "value":  0
                                   }
                    }
    }
]

Second edit

As for the DB. Perhaps the best solution would be to use Sqlite, check the technet wiki about it PowerShell: Accessing SQLite databases

tukan
  • 17,050
  • 1
  • 20
  • 48
  • Wow @tukan that is a very detailed answer. This is similar to the approacd i was trying out. Regarding adding time in registry, that is something i cannot do due the restriction in the application that creates these entries in the registry. I will update the initial question with few clarifications that i need from new understanding of powershell – Aswin Francis Feb 26 '18 at 09:54
  • i have updated the question. Thanks again for your detailed answer. Meanwhile i will try to make use if your answer :) – Aswin Francis Feb 26 '18 at 10:06
  • @AswinFrancis I'm glad you like it. Don't forget to upvote.]. I don't like XML (try `ConvertTo-XML`) too much - it is really a pain to work with. In the original question you gave option with JSON so I took the liberty to do it this way. Maybe it would be easy to create a JSON and then XML from it. – tukan Feb 26 '18 at 10:12
  • JSON is also fine but is there any way to get it working as mentioned in the updated section of the question. I need to keep track of flag updates independently. The XML is just to give a rough idea of the kind of data i am expecting. I leave it to the experts to tell me which is the best way to handle this :) – Aswin Francis Feb 26 '18 at 10:23
  • @AswinFrancis I have update the answer based on your updated question. Now it generates similar structure to your `XML` – tukan Feb 26 '18 at 12:38
  • @AswinFrancis I happy for that! (it took me longer than I expected) Please don't forget to mark it answered - https://stackoverflow.com/help/someone-answers – tukan Feb 28 '18 at 12:31
  • thanks a lot :) i will mark this has answer. I have few more queries that i think you will be able to guide me with so i can get a start. Is there any way i can ask you these? – Aswin Francis Mar 02 '18 at 10:49
  • @AswinFrancis thank you. Well you can always create a new question and I'll try my best to answer it. I did not yet use the chatting here at SO. – tukan Mar 02 '18 at 10:54
  • any reason you dint recommend using a class then create a list of that class and then just calling convert to json on that. Then I would have one file for each client with json in it. Something like below: [ { "IsOn": true, "IsLocked": false, "Name": "Background", "Timestamp": "\/Date(1517661180000)\/" }, { "IsOn": true, "IsLocked": false, "Name": "Carrier", "Timestamp": "\/Date(1517661180000)\/" } ] – Aswin Francis Mar 02 '18 at 12:36
  • @AswinFrancis no perticular reason. I wanted to show the genesis of it all, so you could cherry pick on what you need. – tukan Mar 02 '18 at 12:54
  • how do i access registry of remote machines. Assume i have a list of server and i want to do the above operation on these servers. So i want to do Get-ChildItem $RegKey -Recurse | ForEach-Object { $regkey = (Get-ItemProperty $_.PSPath) | Where-Object { $_.PSPath -match 'debug' }} on list of server – Aswin Francis Mar 07 '18 at 13:36
  • @AswinFrancis use `Import-Module PSRemoteRegistry` see e.g. https://stackoverflow.com/questions/15069130/get-remote-registry-value – tukan Mar 07 '18 at 13:42
  • for this i need to setup the module in the server where the script is run right? Is there no inbuilt way? – Aswin Francis Mar 07 '18 at 13:50
  • @AswinFrancis you could use .NET's `[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey` as is written at the link. – tukan Mar 07 '18 at 13:52
  • i am trying that but i am not able to loop through the registry keys. How do i replace Get-ChildItem "HKLM:\Software\path' -Recurse | ForEach-Object {} with the value from $reg.OpenSubkey($keyname) – Aswin Francis Mar 07 '18 at 13:56
  • @AswinFrancis this is quite different from your original question. You should probably ask separate question as this drifts away. Anyways, You can try `foreach ($subkey in $reg.OpenSubKey($key).GetSubKeyNames())` – tukan Mar 07 '18 at 14:10
  • thanks. Created new question: https://stackoverflow.com/questions/49154280/how-to-iterate-through-remote-registry-keys-using-powershell – Aswin Francis Mar 07 '18 at 14:27