Class: Winreg::Key

Inherits:
Object
  • Object
show all
Defined in:
lib/winreg.rb,
ext/winreg/winreg.c

Overview

An open registry key. Instances come only from Winreg.open/.create and Key#open/#create (no public .new). All methods raise Winreg::Closed on a closed key except close/closed?/path/view/created?/inspect.

Defined Under Namespace

Classes: Info

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#pathObject (readonly)

Canonical full path, long-form root ("HKEY_CURRENT_USER\Software\X").



245
246
247
# File 'lib/winreg.rb', line 245

def path
  @path
end

#viewObject (readonly)

:default | :v64 | :v32



248
249
250
# File 'lib/winreg.rb', line 248

def view
  @view
end

Class Method Details

._create(vroot, vsub, vsam) ⇒ Object

Key._create(root, subpath, sam) — private singleton. Creates all missing intermediate keys; NULL SECURITY_ATTRIBUTES inherits the parent SD.



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'ext/winreg/winreg.c', line 286

static VALUE
key_s_create(VALUE klass, VALUE vroot, VALUE vsub, VALUE vsam)
{
    VALUE obj = key_alloc(cKey);
    key_t *k = key_get(obj);
    HKEY root = root_hkey(vroot);
    DWORD sam = (DWORD)NUM2ULONG(vsam);
    HKEY h = NULL;
    DWORD disp = 0;
    LONG rc;
    WCHAR *w;

    k->view_sam = sam & WOW64_VIEW_MASK;
    w = to_wide(vsub);
    rc = RegCreateKeyExW(root, w, 0, NULL, REG_OPTION_NON_VOLATILE, sam, NULL, &h, &disp);
    xfree(w);
    if (rc != ERROR_SUCCESS) raise_lstatus("RegCreateKeyExW", rc);
    k->h = h;
    k->created = (disp == REG_CREATED_NEW_KEY) ? 1 : 0;
    k->closed = 0;
    return obj;
}

._open(vroot, vsub, vsam) ⇒ Object

Key._open(root, subpath_or_nil, sam) — private singleton. A nil subpath is a bare root: per the documented RegOpenKeyExW behavior a NULL lpSubKey on a predefined key returns the SAME process-global pseudo-handle, so no OS call is made; the Key wraps the predefined handle value with predefined set and #close / the free hook skip RegCloseKey.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'ext/winreg/winreg.c', line 257

static VALUE
key_s_open(VALUE klass, VALUE vroot, VALUE vsub, VALUE vsam)
{
    VALUE obj = key_alloc(cKey);
    key_t *k = key_get(obj);
    HKEY root = root_hkey(vroot);
    DWORD sam = (DWORD)NUM2ULONG(vsam);
    HKEY h = NULL;
    LONG rc;
    WCHAR *w;

    k->view_sam = sam & WOW64_VIEW_MASK;
    if (NIL_P(vsub)) {
        k->h = root;
        k->predefined = 1;
        k->closed = 0;
        return obj;
    }
    w = to_wide(vsub);
    rc = RegOpenKeyExW(root, w, 0, sam, &h);
    xfree(w);
    if (rc != ERROR_SUCCESS) raise_lstatus("RegOpenKeyExW", rc);
    k->h = h;
    k->closed = 0;
    return obj;
}

Instance Method Details

#binary(name) ⇒ Object



319
320
321
# File 'lib/winreg.rb', line 319

def binary(name)
  typed_bytes(name, [TYPES[:binary]], "REG_BINARY")
end

#binary?(name) ⇒ Boolean

Returns:

  • (Boolean)


323
324
325
326
327
# File 'lib/winreg.rb', line 323

def binary?(name)
  binary(name)
rescue Winreg::NotFound
  nil
end

#closeObject



527
528
529
530
531
532
533
534
535
536
537
538
# File 'ext/winreg/winreg.c', line 527

static VALUE
key_close(VALUE self)
{
    key_t *k = key_get(self);
    if (!k->closed) {
        /* bare roots: flag flip only — never RegCloseKey a pseudo-handle */
        if (k->h && !k->predefined) RegCloseKey(k->h);
        k->h = NULL;
        k->closed = 1;
    }
    return Qnil;
}

#closed?Boolean

Returns:

  • (Boolean)


540
# File 'ext/winreg/winreg.c', line 540

static VALUE key_closed_p(VALUE self)  { return key_get(self)->closed ? Qtrue : Qfalse; }

#create(subpath, access: :read_write) ⇒ Object

Child open-or-create, same view rules.



456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/winreg.rb', line 456

def create(subpath, access: :read_write)
  sam = Winreg.send(:access_mask, access)
  k = _create_child(subpath_arg(subpath), sam)
  k.send(:_init_meta, @root, child_subpath(subpath), @view)
  return k unless block_given?

  begin
    yield k
  ensure
    k.close
  end
end

#created?Boolean

Returns:

  • (Boolean)


541
# File 'ext/winreg/winreg.c', line 541

static VALUE key_created_p(VALUE self) { return key_get(self)->created ? Qtrue : Qfalse; }

#delete_key(name, recursive: false) ⇒ Object

Delete the named child subkey (handle-relative; the view is applied via RegDeleteKeyExW samDesired). Non-recursive delete of a key that still has subkeys fails with an OSError; recursive: true performs a deepest- first walk in Ruby over the raise-safe primitives (interruptible; NOT atomic — same as RegDeleteTreeW), with the view applied to every open.



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/winreg.rb', line 474

def delete_key(name, recursive: false)
  n = subpath_arg(name)
  if recursive
    _delete_tree(n)
  else
    begin
      _delete_key(n)
    rescue Winreg::AccessDenied => e
      err = Winreg::AccessDenied.new("#{e.message} (key may have subkeys; use recursive: true)")
      err.instance_variable_set(:@code, e.code)
      raise err
    end
  end
  nil
end

#delete_value(name) ⇒ Object

Delete a value (nil/"" addresses the default value). NotFound if absent.



413
414
415
416
# File 'lib/winreg.rb', line 413

def delete_value(name)
  _delete_value(norm_name(name))
  nil
end

#dword(name) ⇒ Object



289
290
291
# File 'lib/winreg.rb', line 289

def dword(name)
  Winreg.send(:decode_int, typed_bytes(name, [TYPES[:dword]], "REG_DWORD"), 4, false, :dword)
end

#dword?(name) ⇒ Boolean

Returns:

  • (Boolean)


293
294
295
296
297
# File 'lib/winreg.rb', line 293

def dword?(name)
  dword(name)
rescue Winreg::NotFound
  nil
end

#each_keyObject

Yields each subkey name.



509
510
511
512
513
514
515
516
517
518
# File 'lib/winreg.rb', line 509

def each_key
  return enum_for(:each_key) unless block_given?

  i = 0
  while (name = _enum_key(i))
    yield name
    i += 1
  end
  self
end

#each_valueObject

Yields (name, type, value) decoded exactly as #read. Live, snapshot-less, kernel order arbitrary; concurrent mutation may skip or repeat entries. A value whose DATA is malformed raises MalformedValue mid-iteration — use #value_names + #raw for forensic robustness.



496
497
498
499
500
501
502
503
504
505
506
# File 'lib/winreg.rb', line 496

def each_value
  return enum_for(:each_value) unless block_given?

  i = 0
  while (entry = _enum_value(i))
    name, t, bytes = entry
    yield name, Winreg.send(:type_symbol, t), Winreg.send(:decode_value, t, bytes)
    i += 1
  end
  self
end

#infoObject



537
538
539
540
541
# File 'lib/winreg.rb', line 537

def info
  a = _query_info
  t = Time.at((a[5] - Winreg.send(:filetime_epoch_delta)) / 10_000_000r)
  Info.new(a[0], a[2], a[1], a[3], a[4], t).freeze
end

#inspectObject



250
251
252
# File 'lib/winreg.rb', line 250

def inspect
  closed? ? "#<Winreg::Key (closed)>" : "#<Winreg::Key #{@path} view=#{@view.inspect}>"
end

#key?(name) ⇒ Boolean

NotFound -> false; ACCESS_DENIED -> true (the key exists, you may not open it); anything else raises. View-consistent (probe carries the view).

Returns:

  • (Boolean)


427
428
429
430
431
432
433
434
435
# File 'lib/winreg.rb', line 427

def key?(name)
  probe = _open_child(subpath_arg(name), Winreg::KEY_QUERY_VALUE)
  probe.close
  true
rescue Winreg::NotFound
  false
rescue Winreg::AccessDenied
  true
end

#key_namesObject



531
532
533
534
535
# File 'lib/winreg.rb', line 531

def key_names
  out = []
  each_key { |n| out << n }
  out
end

#multi_string(name) ⇒ Object



309
310
311
# File 'lib/winreg.rb', line 309

def multi_string(name)
  Winreg.send(:decode_multi, typed_bytes(name, [TYPES[:multi_sz]], "REG_MULTI_SZ"))
end

#multi_string?(name) ⇒ Boolean

Returns:

  • (Boolean)


313
314
315
316
317
# File 'lib/winreg.rb', line 313

def multi_string?(name)
  multi_string(name)
rescue Winreg::NotFound
  nil
end

#open(subpath, access: :read) ⇒ Object

Child open. Relative path, backslashes allowed ("A\B"). The parent's WOW64 view is inherited automatically and CANNOT be overridden (the API encoding of the view-consistency rule). Block form ensure-closes.



442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/winreg.rb', line 442

def open(subpath, access: :read)
  sam = Winreg.send(:access_mask, access)
  k = _open_child(subpath_arg(subpath), sam)
  k.send(:_init_meta, @root, child_subpath(subpath), @view)
  return k unless block_given?

  begin
    yield k
  ensure
    k.close
  end
end

#qword(name) ⇒ Object



299
300
301
# File 'lib/winreg.rb', line 299

def qword(name)
  Winreg.send(:decode_int, typed_bytes(name, [TYPES[:qword]], "REG_QWORD"), 8, false, :qword)
end

#qword?(name) ⇒ Boolean

Returns:

  • (Boolean)


303
304
305
306
307
# File 'lib/winreg.rb', line 303

def qword?(name)
  qword(name)
rescue Winreg::NotFound
  nil
end

#raw(name) ⇒ Object

Raw escape hatch: NEVER decodes, never raises MalformedValue. -> [Integer type_tag, String bytes (Encoding::BINARY)]



331
332
333
# File 'lib/winreg.rb', line 331

def raw(name)
  _read_raw(norm_name(name))
end

#read(name) ⇒ Object

Generic typed read -> [type, value]. type is a Symbol from Winreg::TYPES (or the raw Integer tag for types outside the table). Strings are returned UNEXPANDED with at most one trailing NUL stripped; :multi_sz as Array; integers range-decoded; :binary/:none/:link/unknown as raw BINARY bytes.



261
262
263
264
# File 'lib/winreg.rb', line 261

def read(name)
  t, bytes = _read_raw(norm_name(name))
  [Winreg.send(:type_symbol, t), Winreg.send(:decode_value, t, bytes)]
end

#read?(name) ⇒ Boolean

Like #read, but nil when the value does not exist.

Returns:

  • (Boolean)


267
268
269
270
271
# File 'lib/winreg.rb', line 267

def read?(name)
  read(name)
rescue Winreg::NotFound
  nil
end

#string(name, expand: false) ⇒ Object

REG_SZ or REG_EXPAND_SZ -> String (UTF-8), unexpanded unless expand: true (ExpandEnvironmentStringsW on the result, whatever the type).



275
276
277
278
279
# File 'lib/winreg.rb', line 275

def string(name, expand: false)
  bytes = typed_bytes(name, [TYPES[:sz], TYPES[:expand_sz]], "REG_SZ/REG_EXPAND_SZ")
  s = Winreg.send(:decode_string, bytes)
  expand ? Winreg.expand_string(s) : s
end

#string?(name, expand: false) ⇒ Boolean

nil for a MISSING value (TypeMismatch still raises — a wrong type is a bug, not an absence). Same pattern for the other ? readers.

Returns:

  • (Boolean)


283
284
285
286
287
# File 'lib/winreg.rb', line 283

def string?(name, expand: false)
  string(name, expand: expand)
rescue Winreg::NotFound
  nil
end

#value?(name) ⇒ Boolean

RegQueryValueExW size probe; error 2 -> false.

Returns:

  • (Boolean)


421
422
423
# File 'lib/winreg.rb', line 421

def value?(name)
  _value_p(norm_name(name))
end

#value_namesObject

Value names only — never decodes data, so hostile values can't abort it.



521
522
523
524
525
526
527
528
529
# File 'lib/winreg.rb', line 521

def value_names
  out = []
  i = 0
  while (entry = _enum_value(i))
    out << entry[0]
    i += 1
  end
  out
end

#watch(subtree: false, filter: :default) ⇒ Object

Without a block: an armed Winreg::Watch (changes between construction and the first #wait are caught). With a block: loops, yielding :changed per coalesced change; yields :deleted once and returns if the watched key is deleted; the Watch is ensure-closed. The Watch opens its OWN private KEY_NOTIFY|view handle from this key's path — closing this Key afterwards does not disturb it.

Raises:



551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
# File 'lib/winreg.rb', line 551

def watch(subtree: false, filter: :default)
  raise Winreg::Closed, "winreg: key is closed" if closed?

  mask = Winreg.send(:filter_mask, filter)
  vsam = Winreg.send(:view_mask, @view || :default)
  w = Watch.send(:_new, @root, @subpath, vsam, subtree ? true : false, mask)
  return w unless block_given?

  begin
    loop do
      event = w.wait
      yield event
      break if event == :deleted
    end
    nil
  ensure
    w.close
  end
end

#write(name, type, value) ⇒ Object

Generic typed write — symmetric with #read; validates identically to the typed writers (it dispatches to them). :dword_be packs 4 bytes BE; :none expects bytes; :link raises (links are read-surface only in v1); a raw Integer tag goes through #write_raw.



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/winreg.rb', line 378

def write(name, type, value)
  case type
  when :sz         then write_string(name, value)
  when :expand_sz  then write_expand_string(name, value)
  when :multi_sz   then write_multi_string(name, value)
  when :dword      then write_dword(name, value)
  when :qword      then write_qword(name, value)
  when :dword_be
    v = Winreg.send(:int_in_range, value, 0xFFFF_FFFF, "REG_DWORD_BIG_ENDIAN")
    _write_raw(norm_name(name), TYPES[:dword_be], [v].pack("N"))
  when :binary     then write_binary(name, value)
  when :none
    Winreg.send(:string_arg, value, ":none data")
    _write_raw(norm_name(name), TYPES[:none], value)
  when :link
    raise ArgumentError, "winreg: :link values are read-surface only (v1); " \
                         "registry symlink creation is out of scope"
  when Integer     then write_raw(name, type, value)
  else
    raise ArgumentError, "winreg: unknown registry type #{type.inspect}"
  end
  nil
end

#write_binary(name, bytes) ⇒ Object



368
369
370
371
372
# File 'lib/winreg.rb', line 368

def write_binary(name, bytes)
  Winreg.send(:string_arg, bytes, "binary data")
  _write_raw(norm_name(name), TYPES[:binary], bytes)
  nil
end

#write_dword(name, int) ⇒ Object



356
357
358
359
360
# File 'lib/winreg.rb', line 356

def write_dword(name, int)
  v = Winreg.send(:int_in_range, int, 0xFFFF_FFFF, "REG_DWORD")
  _write_raw(norm_name(name), TYPES[:dword], [v].pack("V"))
  nil
end

#write_expand_string(name, str) ⇒ Object



346
347
348
349
# File 'lib/winreg.rb', line 346

def write_expand_string(name, str)
  _write_raw(norm_name(name), TYPES[:expand_sz], Winreg.send(:encode_sz, str))
  nil
end

#write_multi_string(name, ary) ⇒ Object



351
352
353
354
# File 'lib/winreg.rb', line 351

def write_multi_string(name, ary)
  _write_raw(norm_name(name), TYPES[:multi_sz], Winreg.send(:encode_multi, ary))
  nil
end

#write_qword(name, int) ⇒ Object



362
363
364
365
366
# File 'lib/winreg.rb', line 362

def write_qword(name, int)
  v = Winreg.send(:int_in_range, int, 0xFFFF_FFFF_FFFF_FFFF, "REG_QWORD")
  _write_raw(norm_name(name), TYPES[:qword], [v].pack("Q<"))
  nil
end

#write_raw(name, type_tag, bytes) ⇒ Object

Raw escape hatch, symmetric with #raw: exact bytes under an arbitrary type tag. No validation beyond bytes being a String + the 4 GiB guard.

Raises:

  • (TypeError)


404
405
406
407
408
409
410
# File 'lib/winreg.rb', line 404

def write_raw(name, type_tag, bytes)
  raise TypeError, "winreg: type tag must be an Integer, got #{type_tag.inspect}" unless type_tag.is_a?(Integer)

  Winreg.send(:string_arg, bytes, "raw data")
  _write_raw(norm_name(name), type_tag, bytes)
  nil
end

#write_string(name, str) ⇒ Object

All writers return nil and require access: :read_write (else the OS raises AccessDenied). The gem owns serialization, so the wire format is correct by construction (double-NUL REG_MULTI_SZ, cb incl. terminators).



341
342
343
344
# File 'lib/winreg.rb', line 341

def write_string(name, str)
  _write_raw(norm_name(name), TYPES[:sz], Winreg.send(:encode_sz, str))
  nil
end