/build/static/layout/Breadcrumb_cap_w.png

VBscript to write lines to services file if not present

Hello,
I'm very new at VBScripts andI need to create a VBScript that add some lines to the services file, but only if they are not already there.
I created a script with the following parameters:
set file = objFSO.opentextfile(strWinDir & "\system32\drivers\etc\services.", 8) 
file.writeline "sapmsPRD         3600/tcp # SAP System Message Port"
file.writeline "sapmsQAS         3600/tcp # SAP System Message Port"
file.writeline "sapmsDV1         3600/tcp # SAP System Message Port"
etc

It creates the lines successfully but if the lines are already in the services file it doesn't recognize that and the lines are created anyway.
Is there a function or something I could use to read if the line is already in the services file and only if not write the line?
thank you in advance

0 Comments   [ + ] Show comments

Answers (2)

Answer Summary:
Posted by: anonymous_9363 8 years ago
Red Belt
0

Top Answer

Here's a class file for handling the SERVICES file.

Don't be put off by the scary word "class" - it's just a fancy way of bundling a set of properties and functions (methods) in one place.

This isn't my code and I've never used it. I came across it eons ago and remember thinking that it was quite well written and would be a useful addition to my armoury.

Option Explicit
Dim arr, n
Dim o_h : Set o_h = New clsServicesFile
Call o_h.Load( "C:\Windows\System32\drivers\etc\Services" , False )
o_h.DumpData
Call o_h.AddServiceEntry( "192.168.1.5" , "A" )
Call o_h.AddServiceEntry( "192.168.1.5" , "B" )
Call o_h.AddServiceEntry( "192.168.1.5" , "C" )
Call o_h.AddServiceEntry( "192.168.1.5" , "D" )
Call o_h.AddServiceEntry( "192.168.1.6" , "E" )
Call o_h.AddServiceEntry( "192.168.1.6" , "F" )
Call o_h.AddServiceEntry( "192.168.1.6" , "G" )
Call o_h.AddServiceEntry( "192.168.1.6" , "H" )
Call o_h.AddServiceEntry( "192.168.1.7" , "I" )
Call o_h.AddServiceEntry( "192.168.1.7" , "J" )
Call o_h.AddServiceEntry( "192.168.1.7" , "K" )
Call o_h.AddServiceEntry( "192.168.1.7" , "L" )
Call o_h.DeleteServiceEntryPort( "192.168.1.5" , "A" )
Call o_h.DeleteServiceEntryPort( "192.168.1.5" , "C" )
o_h.DumpData
Call o_h.GetServiceEntryPorts( "192.168.1.5" , arr )
'// This will show all the Ports for 192.168.1.5
WScript.Echo "Ports for 192.168.1.5"
For Each n In arr
WScript.Echo n
Next
'// This will show if there are multiple addresses for a single port
Call o_h.GetServiceEntryPortAddresses( "A" , arr ) 
WScript.Echo "Addresses for test2"
For Each n In arr
WScript.Echo n
Next
'// This will show all the ports in the Services file
WScript.Echo "All Service Ports"
Call o_h.GetAllServiceEntryPorts( arr )
For Each n In arr
WScript.Echo n
Next
'// This will show all the IP addresses in the Services file
WScript.Echo "All Service ip addresses"
Call o_h.GetAllServiceEntryAddresses( arr )
For Each n In arr
WScript.Echo n
Next
Call o_h.DeleteServiceEntry( "192.168.1.5" )
Call o_h.DeleteServiceEntry( "192.168.1.6" )
Call o_h.DeleteServiceEntry( "192.168.1.7" )
Call o_h.DeleteServiceEntry( "192.168.1.8" )
Call o_h.DeleteServiceEntry( "216.10.194.14" )
o_h.DumpData

Call o_h.Save( "C:\temp\Services" )

Class clsServiceFile
Private dicLines
Private dicServiceName_Map
Private dicPort_Map
Private Sub Class_Initialize()
Set dicLines = CreateObject("Scripting.Dictionary")
Set dicServiceName_Map = CreateObject("Scripting.Dictionary")
Set dicPort_Map = CreateObject("Scripting.Dictionary")
dicPort_Map.CompareMode = vbTextCompare
End Sub

Private Sub Class_Terminate()
Set dicLines = Nothing
Set dicServiceName_Map = Nothing
Set dicPort_Map = Nothing
End Sub
'// Function gets the ServiceName and data
Private Sub ParseLine( ByVal strLine , ByRef strComment , ByRef strServiceName , ByRef strPorts  ) 
Dim objRegEx
Dim objRegExp_Execute

Set objRegEx = New RegExp

objRegEx.Global = False
objRegEx.IgnoreCase = True
objRegEx.Pattern = "\s*(#.*)\s*"

'// Parse Comment
If objRegEx.Test( strLine ) Then
Set objRegExp_Execute = objRegEx.Execute( strLine )
strComment = objRegExp_Execute.Item(0).subMatches.Item(0)
strLine = objRegEx.Replace( strLine , "" )
End If

objRegEx.Pattern = "\s*((\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)|(\s*((([0-9A-F]{1,4}:){7}([0-9A-F]{1,4}|:))|(([0-9A-F]{1,4}:){6}(:[0-9A-F]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-F]{1,4}:){5}(((:[0-9A-F]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-F]{1,4}:){4}(((:[0-9A-F]{1,4}){1,3})|((:[0-9A-F]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-F]{1,4}:){3}(((:[0-9A-F]{1,4}){1,4})|((:[0-9A-F]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-F]{1,4}:){2}(((:[0-9A-F]{1,4}){1,5})|((:[0-9A-F]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-F]{1,4}:){1}(((:[0-9A-F]{1,4}){1,6})|((:[0-9A-F]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-F]{1,4}){1,7})|((:[0-9A-F]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*))\s*"
'// Parse ServiceName
If objRegEx.Test( strLine ) Then
Set objRegExp_Execute = objRegEx.Execute( strLine )
strServiceNameAddress = objRegExp_Execute.Item(0).subMatches.Item(0)
strPorts = objRegEx.Replace( strLine , "" )
End If
End Sub
'// Function gets the ServiceName and data
Private Function GetServiceName( ByRef strLine , ByRef strServiceNameAddress  ) 
GetServiceName = False 
Dim objRegEx 
Dim objRegExp_Execute

Set objRegEx = New RegExp
objRegEx.Global = False
objRegEx.IgnoreCase = True
objRegEx.Pattern = "\s*((\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)|(\s*((([0-9A-F]{1,4}:){7}([0-9A-F]{1,4}|:))|(([0-9A-F]{1,4}:){6}(:[0-9A-F]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-F]{1,4}:){5}(((:[0-9A-F]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-F]{1,4}:){4}(((:[0-9A-F]{1,4}){1,3})|((:[0-9A-F]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-F]{1,4}:){3}(((:[0-9A-F]{1,4}){1,4})|((:[0-9A-F]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-F]{1,4}:){2}(((:[0-9A-F]{1,4}){1,5})|((:[0-9A-F]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-F]{1,4}:){1}(((:[0-9A-F]{1,4}){1,6})|((:[0-9A-F]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-F]{1,4}){1,7})|((:[0-9A-F]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*))\s*"
If objRegEx.Test( strLine ) Then
Set objRegExp_Execute = objRegEx.Execute( strLine )
strServiceNameAddress = objRegExp_Execute.Item(0).subMatches.Item(0)
strLine = objRegEx.Replace( strLine , "" )
GetServiceName = True
End If
End Function
'// Internal function used to generate unique IDs
Private Function GenerateGUID()
Dim objGUID_Generator

Set objGUID_Generator = CreateObject("Scriptlet.TypeLib") 
GenerateGUID = Mid(objGUID_Generator.Guid, 2, 36)
End Function
'// Function gets Ports for an ServiceName address
'// Returns True on success, otherwise False if the ServiceName doesn't exist
Public Function GetServiceEntryPorts( ByVal strServiceNameAddress , ByRef arrPorts )
GetServiceEntryPorts = False

arrPorts = Array
If dicServiceName_Map.Exists( strServiceNameAddress ) Then 
arrPorts = dicLines.Item( dicServiceName_Map.Item(strServiceNameAddress) )(1).Keys
GetServiceEntryPorts = True
End If
End Function 
'// Function gets ServiceName address for an Port
'// Returns True on success, otherwise False if Port doesn't exist
Public Function GetServiceEntryPortAddresses( ByVal strPort , ByRef arrServiceNameAddresses )
GetServiceEntryPortAddresses = False

arrServiceNameAddresses = Array
If dicPort_Map.Exists( strPort ) Then 
arrServiceNameAddresses = dicPort_Map.Item( strPort ).Keys
GetServiceEntryPortAddresses = True
End If
End Function 
'// Removes a Services entry by address, return True on success, otherwise False if the IP doesn't exist
Public Function DeleteServiceEntry( ByVal strIPAddress )
Dim arrIPAddresses
Dim strEntry
DeleteServiceEntry = False

If dicIP_Map.Exists( strIPAddress ) Then 
strEntry = dicIP_Map.Item(strIPAddress)
'// Remove the IPs from the associated Ports
For Each arrIPAddresses In dicLines.Item(strEntry)(1).Keys 
If dicPort_Map.Item( arrIPAddresses ).Exists( strIPAddress ) Then
dicPort_Map.Item( arrIPAddresses ).Remove( strIPAddress )
End If
'// If there are no more assoicated IPs remove the Port
If dicPort_Map.Item(arrIPAddresses).Count = 0 Then
dicPort_Map.Remove( arrIPAddresses )
End If 
Next

'// This *should* exist since we manage the entries and mappings
dicLines.Remove( strEntry )
dicIP_Map.Remove( strIPAddress )
DeleteServiceEntry = True
End If
End Function
'// Removes a Service Port by IPV4 or IPV6 address
Public Function DeleteServiceEntryPort( ByVal strIPAddress , ByVal strPort )
Dim strEntry

DeleteServiceEntryPort = False

'// If the IP is valid
If dicIP_Map.Exists( strIPAddress ) Then
strEntry = dicIP_Map.Item(strIPAddress)

'// If the Port exists remove the IP and if no more IPs are mapped to the Port remove the Port
If dicPort_Map.Exists( strPort ) Then
If dicPort_Map.Item( strPort ).Exists( strIPAddress ) Then
dicPort_Map.Item( strPort ).Remove( strIPAddress )
End If
If dicPort_Map.Item(strPort).Count = 0 Then
dicPort_Map.Remove( strPort )
End If
End If

'// If IP no longer has Ports associated with it remove it
Call DeletePort( strEntry , strPort )
If dicLines.Item( strEntry )(1).Count = 0 Then
dicLines.Remove( strEntry )
dicIP_Map.Remove( strIPAddress )
End If
DeleteServiceEntryPort = True
End If
End Function
'// Adds a Services entry by IPV4 or IPV6 address, Port should be the text Port for the address. 
'// Returns True on success
Public Function AddServiceEntry( ByVal strIPAddress , ByVal strPort )
Dim objRegExp
Dim strGUID

AddServiceEntry = False

Set objRegExp = New RegExp
objRegExp.Global = True
objRegExp.IgnoreCase = True
objRegExp.Pattern = "\s*"

strPort = objRegExp.Replace( strPort , "" )
strIPAddress = objRegExp.Replace( strIPAddress , "" )

'// Validate IP
If IsIPv6( strIPAddress ) Or IsIPv4( strIPAddress ) Then 
'// Check for Port in the Port mapping
If Not dicPort_Map.Exists( strPort ) Then
Call dicPort_Map.Add( strPort , CreateObject("Scripting.Dictionary") )
End If

If Not dicPort_Map.Item( strPort ).Exists( strIPAddress ) Then 
Call dicPort_Map.Item( strPort ).Add( strIPAddress , "" )
End If

'// Map IP -> Port 
If dicIP_Map.Exists( strIPAddress ) Then 
'// Lookup the index by IP then add Ports
Call AddPort( dicIP_Map.Item(strIPAddress) , strPort )
Else
'// Store File Line
strGUID = GenerateGUID
Call dicLines.Add( strGUID , Array( strIPAddress , CreateObject("Scripting.Dictionary") , vbNullString ) )
Call AddPort( strGUID , strPort )
Call dicIP_Map.Add( strIPAddress , strGUID )
End If
AddServiceEntry = True
End If
End Function
'// Should be used for debugging the data
Public Sub DumpData( )
Dim arrLine
Dim strEntry
Dim strIPAddress
Dim arrPortMap

'// Dump file to StdOut
WScript.Echo( "-------------------------------------------------------------------" )
For Each arrLine In dicLines.Keys
If TypeName( dicLines.Item(arrLine) )  = "String" Then
WScript.Echo dicLines.Item(arrLine)
ElseIf TypeName( dicLines.Item(arrLine) ) = "Variant()" Then
WScript.StdOut.Write PadLine( dicLines.Item(arrLine)(0) & "" , " " , 16 )  & Space(8)
For Each strEntry In dicLines.Item(arrLine)(1).Keys
WScript.StdOut.Write strEntry & " " 
Next
WScript.StdOut.Write dicLines.Item(arrLine)(2) & vbCrLf
End If 
Next
WScript.Echo( "-------------------------------------------------------------------" )

For Each strIPAddress In dicIP_Map.Keys
For Each strEntry In dicLines.Item( dicIP_Map.Item(strIPAddress) )(1).Keys
WScript.Echo "IP [" & strIPAddress & "] ID Map {" & dicIP_Map.Item(strIPAddress) & "} --> Port [" & strEntry & "]"
If dicPort_Map.Exists( strEntry ) Then 
For Each arrPortMap In dicPort_Map.Item( strEntry ).Keys
WScript.Echo "Port Key [" & strEntry & "] IP --> [" & arrPortMap & "]"
Next
End If
Next
Next  
End Sub 
'// Internal formatting function for padding Services data
Private Function PadLine ( ByVal strTarget , ByVal strCharacterToPadWith , ByVal lngPadLength ) 
If lngPadLength - Len(strTarget) >= 0 Then 
PadLine = String(lngPadLength - Len(strTarget),strCharacterToPadWith) & strTarget 
Else
PadLine = strTarget
End If
End Function
'// Gets all the IP addresses defined in the Services file
'// Returns True on success, otherwise False
Public Function GetAllServiceEntryAddresses( ByRef arrIPAddresses )
GetAllServiceEntryAddresses = False

arrIPAddresses = Array
If dicIP_Map.Count > 0 Then 
arrIPAddresses = dicIP_Map.Keys
GetAllServiceEntryAddresses = True
End If
End Function
'// Gets all the Ports defined in the Services file
'// Returns True on success, otherwise False
Public Function GetAllServiceEntryPorts( ByRef arrPorts )
GetAllServiceEntryPorts = False

arrPorts = Array
If dicPort_Map.Count > 0 Then 
arrPorts = dicPort_Map.Keys
GetAllServiceEntryPorts = True
End If
End Function

'// Write Services file 
'// Returns True if file could be opened for writing
Public Function Save( ByVal strFile )
Dim arrLine
Dim strEntry
Dim objFSO
Dim objFile
On Error Resume Next
Save = False
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = Nothing
Set objFile = objFSO.OpenTextFile( strFile , 2 , True )

If Not objFile Is Nothing Then 
For Each arrLine In dicLines.Keys
If TypeName( dicLines.Item(arrLine) ) = "String" Then
Call objFile.WriteLine( dicLines.Item(arrLine) )
ElseIf TypeName( dicLines.Item(arrLine) ) = "Variant()" Then
Call objFile.Write( PadLine( dicLines.Item(arrLine)(0) & "" , " " , 16 )  & Space(8) )
For Each strEntry In dicLines.Item(arrLine)(1).Keys
Call objFile.Write( strEntry & " " )
Next
Call objFile.Write( dicLines.Item(arrLine)(2) & vbCrLf )
End If 
Next
Save = True
End If

Set objFSO = Nothing
End Function

'// Read Services file 
'// Returns True if file could be opened for reading
Public Function Load( ByVal strFile , ByVal blnMergeComments )
Dim objRegExp
Dim objFSO
Dim objFile
Dim intPosition
Dim strData
Dim strLine
Dim strIPAddress
Dim strComment
Dim strPort
Dim strEntry
Dim strItem

On Error Resume Next

Load = False

dicLines.RemoveAll()
dicIP_Map.RemoveAll()
dicPort_Map.RemoveAll()

Set objRegExp = New RegExp
objRegExp.Global = True
objRegExp.IgnoreCase = True
objRegExp.Pattern = "\s+"

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = Nothing
Set objFile = objFSO.OpenTextFile( strFile , 1 )
If Not objFile Is Nothing Then 
While Not objFile.AtEndOfStream
intPosition = 0
'// Data will not be modified to preserve the file context
strData = Trim(objFile.ReadLine())

'// Remove all the extra whitespace so we have a single spacing
'// Line will be chopped up to see valid information exists
strLine = Trim(objRegExp.Replace(strData, " "))
strIPAddress = vbNullString
strComment = vbNullString
strPort = vbNullString

'// Check if the line is empty
If strLine <> vbNullString Then
Call ParseLine( strLine , strComment , strIPAddress , strPort )
If IsIPv4( strIPAddress ) Or IsIPv6( strIPAddress ) Then
'// Map Port -> IPs
For Each strEntry In Split(strPort)
If Not dicPort_Map.Exists( strEntry ) Then 
Call dicPort_Map.Add( strEntry , CreateObject("Scripting.Dictionary") )
End If
If Not dicPort_Map.Item( strEntry ).Exists( strIPAddress ) Then Call dicPort_Map.Item( strEntry ).Add( strIPAddress , "" )
Next

'// Map IP -> Port 
If dicIP_Map.Exists( strIPAddress ) Then
strItem = dicIP_Map.Item(strIPAddress)
'// Lookup the index by ip then add Ports
For Each strEntry In Split(strPort)
Call AddPort( strItem , strEntry )
Next
If blnMergeComments Then 
'// Overkill (should be first # )
intPosition = InStr( 1, strComment , "#" , vbTextCompare )
If intPosition <> 0 Then 
'// Replace leading # from dual comment
strComment = Mid( strComment , intPosition + 1 )
'// Merge comments
Call SetComment( strItem , dicLines.Item( strItem )(2) & "," & strComment )
End If
End If
Else
'// Store File Line
strItem = GenerateGUID
Call dicLines.Add( strItem , Array( strIPAddress , CreateObject("Scripting.Dictionary") , strComment ) )
For Each strEntry In Split(strPort)
Call AddPort( strItem , strEntry )
Next
Call dicIP_Map.Add( strIPAddress , strItem )
End If
Else '// Unknown IP format or malformed file
Call dicLines.Add(GenerateGUID,strData)
End If
Else
Call dicLines.Add(GenerateGUID,strData)
End If
Wend

Load = True
End If
Set objFile = Nothing
Set objFSO = Nothing
End Function
Private Function SetIP( ByVal strEntry , ByVal strIPAddress )
SetIP = False

If dicLines.Exists( strEntry ) Then
dicLines.Item(strEntry) = Array( strIPAddress , dicLines.Item(strEntry)(1) , dicLines.Item(strEntry)(2) ) 
SetIP = True
End If
End Function
Private Function SetComment( ByVal strEntry , ByVal strComment )
Dim strItem

SetComment = False
If dicLines.Exists( strEntry ) Then
strItem = dicLines.Item(strEntry)
dicLines.Item(strEntry) = Array( dicLines.Item(strEntry)(0) , dicLines.Item(strEntry)(1) , strComment )  
SetComment = True
End If
End Function
Private Function AddPort( ByVal strEntry , ByVal strPort )
AddPort = False

If dicLines.Exists( strEntry ) Then
If Not dicLines.Item(strEntry)(1).Exists( strPort ) Then 
Call dicLines.Item(strEntry)(1).Add( strPort , "" )
AddPort = True
End If
End If
End Function
Private Function DeletePort( ByVal strEntry , ByVal strPort )
DeletePort = False

If dicLines.Exists( strEntry ) Then
If dicLines.Item(strEntry)(1).Exists( strPort ) Then 
Call dicLines.Item(strEntry)(1).Remove( strPort )
DeletePort = CBool( Not dicLines.Item(strEntry)(1).Exists( strPort ) )
End If
End If
End Function
End Class

Posted by: anonymous_9363 8 years ago
Red Belt
0

Also, I can't tell you how painful it was, getting the 'Code' style applied to the text, using my phone (the client I'm working for seems to be able to block certain functionality in IT Ninja and other sites so editing posts using their PCs doesn't work.)

I crashed Firefox twice and Chrome's textareas scroll *incredibly* slowly.

Anyway. I hope it works for you.


Comments:
  • lol! that sounds terrible, but I really appreciate the time and effort you took to enter this answer, this really helps! great stuff. thanks a lot VBScab :) - gmassamo 8 years ago
 
This website uses cookies. By continuing to use this site and/or clicking the "Accept" button you are providing consent Quest Software and its affiliates do NOT sell the Personal Data you provide to us either when you register on our websites or when you do business with us. For more information about our Privacy Policy and our data protection efforts, please visit GDPR-HQ