win32-taskscheduler 0.2.0 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/win32-taskscheduler.rb +1 -0
- data/lib/win32/taskscheduler.rb +1434 -1677
- data/lib/win32/taskscheduler/constants.rb +266 -0
- data/lib/win32/taskscheduler/helper.rb +51 -0
- data/lib/win32/taskscheduler/sid.rb +132 -0
- data/lib/win32/taskscheduler/time_calc_helper.rb +163 -0
- data/lib/win32/taskscheduler/version.rb +6 -0
- metadata +91 -48
- data/CHANGES +0 -51
- data/MANIFEST +0 -11
- data/README +0 -68
- data/Rakefile +0 -54
- data/doc/taskscheduler.txt +0 -419
- data/examples/taskscheduler_example.rb +0 -56
- data/test/test_taskscheduler.rb +0 -524
- data/win32-taskscheduler.gemspec +0 -36
@@ -0,0 +1,266 @@
|
|
1
|
+
require_relative "sid"
|
2
|
+
|
3
|
+
module Win32
|
4
|
+
class TaskScheduler
|
5
|
+
module TaskSchedulerConstants
|
6
|
+
SERVICE_ACCOUNT_USERS = SID::SERVICE_ACCOUNT_USERS
|
7
|
+
|
8
|
+
BUILT_IN_GROUPS = SID::BUILT_IN_GROUPS
|
9
|
+
|
10
|
+
SYSTEM_USERS = SERVICE_ACCOUNT_USERS + BUILT_IN_GROUPS
|
11
|
+
|
12
|
+
# Triggers
|
13
|
+
|
14
|
+
# Trigger is set to run the task a single time
|
15
|
+
TASK_TIME_TRIGGER_ONCE = 1
|
16
|
+
|
17
|
+
# Trigger is set to run the task on a daily interval
|
18
|
+
TASK_TIME_TRIGGER_DAILY = 2
|
19
|
+
|
20
|
+
# Trigger is set to run the task on specific days of a specific week & month
|
21
|
+
TASK_TIME_TRIGGER_WEEKLY = 3
|
22
|
+
|
23
|
+
# Trigger is set to run the task on specific day(s) of the month
|
24
|
+
TASK_TIME_TRIGGER_MONTHLYDATE = 4
|
25
|
+
|
26
|
+
# Trigger is set to run the task on specific day(s) of the month
|
27
|
+
TASK_TIME_TRIGGER_MONTHLYDOW = 5
|
28
|
+
|
29
|
+
# Trigger is set to run the task if the system remains idle for the amount
|
30
|
+
# of time specified by the idle wait time of the task
|
31
|
+
TASK_EVENT_TRIGGER_ON_IDLE = 6
|
32
|
+
|
33
|
+
TASK_TRIGGER_REGISTRATION = 7
|
34
|
+
|
35
|
+
# Trigger is set to run the task at system startup
|
36
|
+
TASK_EVENT_TRIGGER_AT_SYSTEMSTART = 8
|
37
|
+
|
38
|
+
# Trigger is set to run the task when a user logs on
|
39
|
+
TASK_EVENT_TRIGGER_AT_LOGON = 9
|
40
|
+
|
41
|
+
TASK_TRIGGER_SESSION_STATE_CHANGE = 11
|
42
|
+
|
43
|
+
# Daily Tasks
|
44
|
+
|
45
|
+
# The task will run on Sunday
|
46
|
+
TASK_SUNDAY = 0x1
|
47
|
+
|
48
|
+
# The task will run on Monday
|
49
|
+
TASK_MONDAY = 0x2
|
50
|
+
|
51
|
+
# The task will run on Tuesday
|
52
|
+
TASK_TUESDAY = 0x4
|
53
|
+
|
54
|
+
# The task will run on Wednesday
|
55
|
+
TASK_WEDNESDAY = 0x8
|
56
|
+
|
57
|
+
# The task will run on Thursday
|
58
|
+
TASK_THURSDAY = 0x10
|
59
|
+
|
60
|
+
# The task will run on Friday
|
61
|
+
TASK_FRIDAY = 0x20
|
62
|
+
|
63
|
+
# The task will run on Saturday
|
64
|
+
TASK_SATURDAY = 0x40
|
65
|
+
|
66
|
+
# Weekly tasks
|
67
|
+
|
68
|
+
# The task will run between the 1st and 7th day of the month
|
69
|
+
TASK_FIRST_WEEK = 0x01
|
70
|
+
|
71
|
+
# The task will run between the 8th and 14th day of the month
|
72
|
+
TASK_SECOND_WEEK = 0x02
|
73
|
+
|
74
|
+
# The task will run between the 15th and 21st day of the month
|
75
|
+
TASK_THIRD_WEEK = 0x04
|
76
|
+
|
77
|
+
# The task will run between the 22nd and 28th day of the month
|
78
|
+
TASK_FOURTH_WEEK = 0x08
|
79
|
+
|
80
|
+
# The task will run the last seven days of the month
|
81
|
+
TASK_LAST_WEEK = 0x10
|
82
|
+
|
83
|
+
# Monthly tasks
|
84
|
+
|
85
|
+
# The task will run in January
|
86
|
+
TASK_JANUARY = 0x1
|
87
|
+
|
88
|
+
# The task will run in February
|
89
|
+
TASK_FEBRUARY = 0x2
|
90
|
+
|
91
|
+
# The task will run in March
|
92
|
+
TASK_MARCH = 0x4
|
93
|
+
|
94
|
+
# The task will run in April
|
95
|
+
TASK_APRIL = 0x8
|
96
|
+
|
97
|
+
# The task will run in May
|
98
|
+
TASK_MAY = 0x10
|
99
|
+
|
100
|
+
# The task will run in June
|
101
|
+
TASK_JUNE = 0x20
|
102
|
+
|
103
|
+
# The task will run in July
|
104
|
+
TASK_JULY = 0x40
|
105
|
+
|
106
|
+
# The task will run in August
|
107
|
+
TASK_AUGUST = 0x80
|
108
|
+
|
109
|
+
# The task will run in September
|
110
|
+
TASK_SEPTEMBER = 0x100
|
111
|
+
|
112
|
+
# The task will run in October
|
113
|
+
TASK_OCTOBER = 0x200
|
114
|
+
|
115
|
+
# The task will run in November
|
116
|
+
TASK_NOVEMBER = 0x400
|
117
|
+
|
118
|
+
# The task will run in December
|
119
|
+
TASK_DECEMBER = 0x800
|
120
|
+
|
121
|
+
# Flags
|
122
|
+
|
123
|
+
# Used when converting AT service jobs into work items
|
124
|
+
TASK_FLAG_INTERACTIVE = 0x1
|
125
|
+
|
126
|
+
# The work item will be deleted when there are no more scheduled run times
|
127
|
+
TASK_FLAG_DELETE_WHEN_DONE = 0x2
|
128
|
+
|
129
|
+
# The work item is disabled. Useful for temporarily disabling a task
|
130
|
+
TASK_FLAG_DISABLED = 0x4
|
131
|
+
|
132
|
+
# The work item begins only if the computer is not in use at the scheduled
|
133
|
+
# start time
|
134
|
+
TASK_FLAG_START_ONLY_IF_IDLE = 0x10
|
135
|
+
|
136
|
+
# The work item terminates if the computer makes an idle to non-idle
|
137
|
+
# transition while the work item is running
|
138
|
+
TASK_FLAG_KILL_ON_IDLE_END = 0x20
|
139
|
+
|
140
|
+
# The work item does not start if the computer is running on battery power
|
141
|
+
TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40
|
142
|
+
|
143
|
+
# The work item ends, and the associated application quits, if the computer
|
144
|
+
# switches to battery power
|
145
|
+
TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80
|
146
|
+
|
147
|
+
# The work item starts only if the computer is in a docking station
|
148
|
+
TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100
|
149
|
+
|
150
|
+
# The work item created will be hidden
|
151
|
+
TASK_FLAG_HIDDEN = 0x200
|
152
|
+
|
153
|
+
# The work item runs only if there is a valid internet connection
|
154
|
+
TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400
|
155
|
+
|
156
|
+
# The work item starts again if the computer makes a non-idle to idle
|
157
|
+
# transition
|
158
|
+
TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800
|
159
|
+
|
160
|
+
# The work item causes the system to be resumed, or awakened, if the
|
161
|
+
# system is running on batter power
|
162
|
+
TASK_FLAG_SYSTEM_REQUIRED = 0x1000
|
163
|
+
|
164
|
+
# The work item runs only if a specified account is logged on interactively
|
165
|
+
TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000
|
166
|
+
|
167
|
+
# Triggers
|
168
|
+
|
169
|
+
# The task will stop at some point in time
|
170
|
+
TASK_TRIGGER_FLAG_HAS_END_DATE = 0x1
|
171
|
+
|
172
|
+
# The task can be stopped at the end of the repetition period
|
173
|
+
TASK_TRIGGER_FLAG_KILL_AT_DURATION_END = 0x2
|
174
|
+
|
175
|
+
# The task trigger is disabled
|
176
|
+
TASK_TRIGGER_FLAG_DISABLED = 0x4
|
177
|
+
|
178
|
+
# Run Level Types
|
179
|
+
# Tasks will be run with the least privileges
|
180
|
+
TASK_RUNLEVEL_LUA = 0
|
181
|
+
# Tasks will be run with the highest privileges
|
182
|
+
TASK_RUNLEVEL_HIGHEST = 1
|
183
|
+
|
184
|
+
# Logon Types
|
185
|
+
# Used for non-NT credentials
|
186
|
+
TASK_LOGON_NONE = 0
|
187
|
+
# Use a password for logging on the user
|
188
|
+
TASK_LOGON_PASSWORD = 1
|
189
|
+
# The service will log the user on using Service For User
|
190
|
+
TASK_LOGON_S4U = 2
|
191
|
+
# Task will be run only in an existing interactive session
|
192
|
+
TASK_LOGON_INTERACTIVE_TOKEN = 3
|
193
|
+
# Group activation. The groupId field specifies the group
|
194
|
+
TASK_LOGON_GROUP = 4
|
195
|
+
# When Local System, Local Service, or Network Service account is
|
196
|
+
# being used as a security context to run the task
|
197
|
+
TASK_LOGON_SERVICE_ACCOUNT = 5
|
198
|
+
# Not in use; currently identical to TASK_LOGON_PASSWORD
|
199
|
+
TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD = 6
|
200
|
+
|
201
|
+
TASK_MAX_RUN_TIMES = 1440
|
202
|
+
TASKS_TO_RETRIEVE = 5
|
203
|
+
|
204
|
+
# Task creation
|
205
|
+
|
206
|
+
TASK_VALIDATE_ONLY = 0x1
|
207
|
+
TASK_CREATE = 0x2
|
208
|
+
TASK_UPDATE = 0x4
|
209
|
+
TASK_CREATE_OR_UPDATE = 0x6
|
210
|
+
TASK_DISABLE = 0x8
|
211
|
+
TASK_DONT_ADD_PRINCIPAL_ACE = 0x10
|
212
|
+
TASK_IGNORE_REGISTRATION_TRIGGERS = 0x20
|
213
|
+
|
214
|
+
# Priority classes
|
215
|
+
|
216
|
+
REALTIME_PRIORITY_CLASS = 0
|
217
|
+
HIGH_PRIORITY_CLASS = 1
|
218
|
+
ABOVE_NORMAL_PRIORITY_CLASS = 2 # Or 3
|
219
|
+
NORMAL_PRIORITY_CLASS = 4 # Or 5, 6
|
220
|
+
BELOW_NORMAL_PRIORITY_CLASS = 7 # Or 8
|
221
|
+
IDLE_PRIORITY_CLASS = 9 # Or 10
|
222
|
+
|
223
|
+
CLSCTX_INPROC_SERVER = 0x1
|
224
|
+
CLSID_CTask = [0x148BD520, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
|
225
|
+
CLSID_CTaskScheduler = [0x148BD52A, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
|
226
|
+
IID_ITaskScheduler = [0x148BD527, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
|
227
|
+
IID_ITask = [0x148BD524, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
|
228
|
+
IID_IPersistFile = [0x0000010b, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46].pack("LSSC8")
|
229
|
+
|
230
|
+
# Days of month
|
231
|
+
|
232
|
+
TASK_FIRST = 0x01
|
233
|
+
TASK_SECOND = 0x02
|
234
|
+
TASK_THIRD = 0x04
|
235
|
+
TASK_FOURTH = 0x08
|
236
|
+
TASK_FIFTH = 0x10
|
237
|
+
TASK_SIXTH = 0x20
|
238
|
+
TASK_SEVENTH = 0x40
|
239
|
+
TASK_EIGHTH = 0x80
|
240
|
+
TASK_NINETH = 0x100
|
241
|
+
TASK_TENTH = 0x200
|
242
|
+
TASK_ELEVENTH = 0x400
|
243
|
+
TASK_TWELFTH = 0x800
|
244
|
+
TASK_THIRTEENTH = 0x1000
|
245
|
+
TASK_FOURTEENTH = 0x2000
|
246
|
+
TASK_FIFTEENTH = 0x4000
|
247
|
+
TASK_SIXTEENTH = 0x8000
|
248
|
+
TASK_SEVENTEENTH = 0x10000
|
249
|
+
TASK_EIGHTEENTH = 0x20000
|
250
|
+
TASK_NINETEENTH = 0x40000
|
251
|
+
TASK_TWENTIETH = 0x80000
|
252
|
+
TASK_TWENTY_FIRST = 0x100000
|
253
|
+
TASK_TWENTY_SECOND = 0x200000
|
254
|
+
TASK_TWENTY_THIRD = 0x400000
|
255
|
+
TASK_TWENTY_FOURTH = 0x800000
|
256
|
+
TASK_TWENTY_FIFTH = 0x1000000
|
257
|
+
TASK_TWENTY_SIXTH = 0x2000000
|
258
|
+
TASK_TWENTY_SEVENTH = 0x4000000
|
259
|
+
TASK_TWENTY_EIGHTH = 0x8000000
|
260
|
+
TASK_TWENTY_NINTH = 0x10000000
|
261
|
+
TASK_THIRTYETH = 0x20000000
|
262
|
+
TASK_THIRTY_FIRST = 0x40000000
|
263
|
+
TASK_LAST = 0x80000000
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "ffi"
|
2
|
+
|
3
|
+
module Win32
|
4
|
+
class TaskScheduler
|
5
|
+
module Helper
|
6
|
+
extend FFI::Library
|
7
|
+
|
8
|
+
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
|
9
|
+
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
|
10
|
+
FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF
|
11
|
+
|
12
|
+
ffi_lib :kernel32, :advapi32
|
13
|
+
|
14
|
+
attach_function :FormatMessage, :FormatMessageA,
|
15
|
+
%i{ulong pointer ulong ulong pointer ulong pointer}, :ulong
|
16
|
+
|
17
|
+
attach_function :ConvertStringSidToSidW, %i{pointer pointer}, :bool
|
18
|
+
attach_function :LookupAccountSidW, %i{pointer pointer pointer pointer pointer pointer pointer}, :bool
|
19
|
+
attach_function :LocalFree, [:pointer], :pointer
|
20
|
+
|
21
|
+
def win_error(function, err = FFI.errno)
|
22
|
+
err_msg = ""
|
23
|
+
flags = FORMAT_MESSAGE_IGNORE_INSERTS |
|
24
|
+
FORMAT_MESSAGE_FROM_SYSTEM |
|
25
|
+
FORMAT_MESSAGE_MAX_WIDTH_MASK
|
26
|
+
|
27
|
+
# 0x0409 == MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)
|
28
|
+
# We use English for errors because Ruby uses English for errors.
|
29
|
+
|
30
|
+
FFI::MemoryPointer.new(:char, 1024) do |buf|
|
31
|
+
len = FormatMessage(flags, nil, err, 0x0409, buf, buf.size, nil)
|
32
|
+
err_msg = function + ": " + buf.read_string(len).strip
|
33
|
+
end
|
34
|
+
|
35
|
+
err_msg
|
36
|
+
end
|
37
|
+
|
38
|
+
def ole_error(function, err)
|
39
|
+
regex = /OLE error code:(.*?)\sin/
|
40
|
+
match = regex.match(err.to_s)
|
41
|
+
|
42
|
+
if match
|
43
|
+
error = match.captures.first.hex
|
44
|
+
win_error(function, error)
|
45
|
+
else
|
46
|
+
"#{function}: #{err}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
class Pointer
|
5
|
+
def read_wstring(num_wchars = nil)
|
6
|
+
if num_wchars.nil?
|
7
|
+
# Find the length of the string
|
8
|
+
length = 0
|
9
|
+
last_char = nil
|
10
|
+
while last_char != "\000\000"
|
11
|
+
length += 1
|
12
|
+
last_char = get_bytes(0, length * 2)[-2..-1]
|
13
|
+
end
|
14
|
+
|
15
|
+
num_wchars = length
|
16
|
+
end
|
17
|
+
|
18
|
+
wide_to_utf8(get_bytes(0, num_wchars * 2))
|
19
|
+
end
|
20
|
+
|
21
|
+
def wide_to_utf8(wstring)
|
22
|
+
# ensure it is actually UTF-16LE
|
23
|
+
# Ruby likes to mark binary data as ASCII-8BIT
|
24
|
+
wstring = wstring.force_encoding("UTF-16LE")
|
25
|
+
|
26
|
+
# encode it all as UTF-8 and remove trailing CRLF and NULL characters
|
27
|
+
wstring.encode("UTF-8").strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Win32
|
33
|
+
class TaskScheduler
|
34
|
+
module SID
|
35
|
+
extend Win32::TaskScheduler::Helper
|
36
|
+
ERROR_INSUFFICIENT_BUFFER = 122
|
37
|
+
|
38
|
+
def self.LocalSystem
|
39
|
+
from_string_sid("S-1-5-18")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.NtLocal
|
43
|
+
from_string_sid("S-1-5-19")
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.NtNetwork
|
47
|
+
from_string_sid("S-1-5-20")
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.BuiltinAdministrators
|
51
|
+
from_string_sid("S-1-5-32-544")
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.BuiltinUsers
|
55
|
+
from_string_sid("S-1-5-32-545")
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.Guests
|
59
|
+
from_string_sid("S-1-5-32-546")
|
60
|
+
end
|
61
|
+
|
62
|
+
# Converts a string-format security identifier (SID) into a valid, functional SID
|
63
|
+
# and returns a hash with account_name and account_simple_name
|
64
|
+
# @see https://docs.microsoft.com/en-us/windows/desktop/api/sddl/nf-sddl-convertstringsidtosidw
|
65
|
+
#
|
66
|
+
def self.from_string_sid(string_sid)
|
67
|
+
result = FFI::MemoryPointer.new :pointer
|
68
|
+
unless ConvertStringSidToSidW(utf8_to_wide(string_sid), result)
|
69
|
+
raise FFI::LastError.error
|
70
|
+
end
|
71
|
+
result_pointer = result.read_pointer
|
72
|
+
domain, name, use = account(result_pointer)
|
73
|
+
LocalFree(result_pointer)
|
74
|
+
account_names(domain, name, use)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.utf8_to_wide(ustring)
|
78
|
+
# ensure it is actually UTF-8
|
79
|
+
# Ruby likes to mark binary data as ASCII-8BIT
|
80
|
+
ustring = (ustring + "").force_encoding("UTF-8")
|
81
|
+
|
82
|
+
# ensure we have the double-null termination Windows Wide likes
|
83
|
+
ustring += "\000\000" if ustring.empty? || ustring[-1].chr != "\000"
|
84
|
+
|
85
|
+
# encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
|
86
|
+
ustring.encode("UTF-16LE")
|
87
|
+
end
|
88
|
+
|
89
|
+
# Accepts a security identifier (SID) as input.
|
90
|
+
# It retrieves the name of the account for this SID and the name of the
|
91
|
+
# first domain on which this SID is found
|
92
|
+
# @see https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountsidw
|
93
|
+
#
|
94
|
+
def self.account(sid)
|
95
|
+
sid = sid.pointer if sid.respond_to?(:pointer)
|
96
|
+
name_size = FFI::Buffer.new(:long).write_long(0)
|
97
|
+
referenced_domain_name_size = FFI::Buffer.new(:long).write_long(0)
|
98
|
+
|
99
|
+
if LookupAccountSidW(nil, sid, nil, name_size, nil, referenced_domain_name_size, nil)
|
100
|
+
raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountSid, and got no error!"
|
101
|
+
elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
|
102
|
+
raise FFI::LastError.error
|
103
|
+
end
|
104
|
+
|
105
|
+
name = FFI::MemoryPointer.new :char, (name_size.read_long * 2)
|
106
|
+
referenced_domain_name = FFI::MemoryPointer.new :char, (referenced_domain_name_size.read_long * 2)
|
107
|
+
use = FFI::Buffer.new(:long).write_long(0)
|
108
|
+
unless LookupAccountSidW(nil, sid, name, name_size, referenced_domain_name, referenced_domain_name_size, use)
|
109
|
+
raise FFI::LastError.error
|
110
|
+
end
|
111
|
+
[referenced_domain_name.read_wstring(referenced_domain_name_size.read_long), name.read_wstring(name_size.read_long), use.read_long]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Formats domain, name and returns a hash with
|
115
|
+
# account_name and account_simple_name
|
116
|
+
#
|
117
|
+
def self.account_names(domain, name, _use)
|
118
|
+
account_name = !domain.to_s.empty? ? "#{domain}\\#{name}" : name
|
119
|
+
account_simple_name = name
|
120
|
+
{ account_name: account_name, account_simple_name: account_simple_name }
|
121
|
+
end
|
122
|
+
|
123
|
+
SERVICE_ACCOUNT_USERS = [self.LocalSystem, self.NtLocal, self.NtNetwork].map do |user|
|
124
|
+
[user[:account_simple_name].upcase, user[:account_name].upcase]
|
125
|
+
end.flatten.freeze
|
126
|
+
|
127
|
+
BUILT_IN_GROUPS = [self.BuiltinAdministrators, self.BuiltinUsers, self.Guests].map do |user|
|
128
|
+
[user[:account_simple_name].upcase, user[:account_name].upcase]
|
129
|
+
end.flatten.freeze
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|