Belajar Memahami udev Di Linux

Salah satu tugas sistem operasi adalah mengelola perangkat keras yang terhubung pada komputer. Pada versi awal Linux, perangkat yang terhubung dengan komputer akan didaftarkan di directory /dev. Sebagai contoh, perangkat harddisk IDE zaman dulu (yang kini semakin langka) biasanya dikenali dengan nama seperti /dev/hda, /dev/hdb, dan seterusnya. Partisi di dalam harddisk pertama dikenali dengan nama seperti /dev/hda1, /dev/hda2 dan seterusnya. Pada harddisk SATA modern, perangkat harddisk memiliki nama seperti /dev/sda, /dev/sdb dan sebagainya. Bila saya mencolokkan perangkat USB Flash Drive (UFD), akan ada tambahan perangkat baru seperti /dev/sdc.

Lalu mengapa beralih ke udev? Komputer modern umumnya memiliki banyak port USB dimana pengguna bisa mencolok dan melepas perangkat kapan saja. Bila masih tetap memakai cara lama, maka nama sebuah perangkat di /dev akan sulit dicari karena bisa berubah tergantung pada urutan saat ia dikenali. Oleh sebab itu, Linux memakai udev untuk memetakan perangkat yang dikenali kernel dengan sebuah nama yang statis.

Bagaimana cara udev memetakan sebuah perangkat keras ke sebuah nama statis? Jawabannya adalah melalui rules yang ditulis oleh pengguna atau pembuat distro. Pengguna bisa menambahkan file rules di lokasi /etc/udev/rules.d. Distro Linux biasanya sudah dilengkapi dengan segudang rules untuk perangkat keras yang umum dipakai di lokasi /lib/udev/rules.d. Nomor di depan nama file dipakai untuk menentukan prioritas dari rules yang ada.

Sebagai contoh, berikut ini adalah salah satu baris di file /lib/udev/rules.d/60-persistence-storage.rules bawaan UBuntu:

# probe filesystem metadata of disks
KERNEL!="sr*", IMPORT{builtin}="blkid"
...
ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}"

IMPORT akan mengerjakan sesuatu untuk memperoleh sebuah nilai yang nantinya dapat dibaca dengan ENV. Ia bisa mengerjakan program eksternal seperti pada IMPORT{program}, memanggil function built-in seperti pada IMPORT{builtin}, membaca isi file seperti pada IMPORT{file} dan sebagainya. SYMLINK akan membuat sebuah symbolic link sehingga perangkat sesuai dengan nama kernel tetap ada.

Sebagai hasilnya, saya bisa mencari nama partisi berdasarkan label, seperti pada:

$ ls /dev/disk/by-label/

Karena rules menggunakan SYMLINK, maka hasil yang saya jumpai adalah symbolic link ke nama seperti /dev/sda, /dev/sdb dan sebagainya. Dengan demikian, saya tetap bisa mencari perangkat berdasarkan cara lama (seperti /dev/sda) ataupun memakai cara yang lebih konsisten (seperti berdasarkan id, label, dan sebagainya).

Salah satu fitur menarik dari udev adalah ia bisa mengerjakan sebuah program pada saat perangkat dicolok atau dilepaskan dengan menggunakan RUN. Sebagai latihan, saya akan membuat sebuah rule yang akan mengerjakan program untuk menulis ke sebuah file setiap kali ada perangkat Samsung yang dicolokkan melalui USB.

Langkah pertama yang perlu saya lakukan adalah melihat informasi apa saja yang dikenali oleh kernel. Untuk itu, saya mencolokkan perangkat dan memberikan perintah:

$ lsusb
Bus 002 Device 003: ID xxxx:xxxx SanDisk Corp.
Bus 002 Device 005: ID xxxx:xxxx Samsung Electronics Co., Ltd
...

Berdasarkan informasi tersebut (Bus 002 Device 005), saya mengetahui bahwa perangkat dipetakan ke file /dev/bus/usb/002/005. Untuk melihat atribut apa saja yang ada, saya dapat memberikan perintah berikut ini:

$ udevadm info --name=/dev/bus/usb/002/005 --query=property
...
ID_VENDOR_ID=04e8
...

Nilai 04e8 adalah USB Vendor ID (VID) untuk Samsung. Selain memakai cara manual, saya juga bisa menemukan VID untuk seluruh pembuat perangkat USB dengan mencari nama perusahaan di http://www.usb.org/developers/tools/comp_dump. Sebagai informasi, setiap perusahaan yang ingin menciptakan perangkat keras yang memakai USB harus membayar sekitar U$5000 untuk memperoleh VID ini.

Saya kemudian membuat sebuah file baru di folder /etc/udev/rules.d dengan nama 10-latihan.rules yang isinya seperti berikut ini:

SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", RUN+="/home/snake/latihan.sh %p %E{ID_MODEL}"

Rules di atas akan mengerjakan script /home/snake/latihan.sh bila ada perangkat dengan ID_VENDOR 04e8 dihubungkan ke USB. Di dalam RUN, saya dapat memakai string substitution seperti %p yang akan diganti menjadi nama perangkat sesuai yang dikenali oleh kernel, %n mewakili nomor perangkat, dan %E{...} untuk mengakses property dari perangkat tersebut.

Langkah berikutnya, saya membuat sebuah script sederhana di lokasi /home/snake/latihan.sh yang isinya seperti berikut ini:

#!/bin/sh
echo `date`: Perangkat Samsung Ditemukan Pada "$@" >> /home/snake/log.txt

Sekarang, setiap kali sebuah perangkat Samsung dihubungkan ke PC melalui USB, akan ada baris baru di /home/snake/log.txt seperti:

Fri Feb 7 10:20:23 WIB 2015: Perangkat Samsung Ditemukan Pada /devices/xxx SAMSUNG_Android  
Iklan

Menjalankan File ISO Linux Dari Live USB Tanpa Instalasi

Saya sudah lama meninggalkan CD/DVD sebagai media boot sistem operasi Linux. Sebagai gantinya, saya mem-boot Linux dari perangkat USB Flash Drive (UFD). Cara ini sangat praktis dan lebih cepat dibandingkan dengan memakai media CD/DVD. Tapi masalah yang kemudian timbul adalah bagaimana bila saya sering memakai beberapa distro Linux yang berbeda? Membeli 1 USB Flash Drive (UFD) baru khusus untuk 1 distro Linux adalah hal yang mubazir mengingat file ISO untuk Live USB biasanya memiliki ukuran kecil.

Saya bisa saja membuat beberapa partisi berbeda di UFD, lalu men-install distro Linux yang berbeda di masing-masing partisi yang ada. Akan tetapi, pada artikel ini, saya akan mencoba sebuah cara yang lebih ringkas. Saya akan men-boot Linux langsung dari file ISO yang dijumpai di UFD. Ini merupakan fitur baru dari GRUB 2. Mengapa memilih men-boot langsung dari ISO ketimbang men-install ke partisi? Salah satu keuntungannya adalah saya bisa melakukan upgrade cukup dengan mengganti ke file ISO terbaru.

Saat ini ada beberapa tools bagi pengguna awam untuk membuat Live USB yang membaca file ISO secara langsung. Tidak seperti end-user, sebagai seorang developer, saya perlu memahami mekanisme yang terjadi. Oleh sebab itu, saya akan mencoba mem-boot file ISO SystemRescue-Cd dan Fedora yang sudah saya download. Saya perlu meletakkan file tersebut pada sebuah partisi di UFD. Sebagai latihan, saya akan meletakkannya pada sebuah partisi NTFS di folder iso. Partisi ini adalah partisi pertama sehingga saya tetap dapat menggunakannya untuk baca tulis data di sistem operasi Windows (tidak seperti Linux, Windows hanya bersedia mengenali partisi pertama dari UFD). Btw, saya memakai NTFS karena FAT32 memiliki keterbatasan dimana file memiliki ukuran maksimal 4 GB.

Bootstrap loader adalah komponen sistem operasi yang dijalankan pertama kali pada saat komputer dinyalakan. Biasanya bootstrap loader di-install pada sektor pertama harddisk (disebut juga Master Boot Record) dan sektor pertama partisi (disebut juga Boot Sector). Btw, ini sedikit berbeda pada komputer baru yang sudah mendukung Unified Extensible Firmware Interface (UEFI) sebagai pengganti BIOS. UEFI mendukung layout partisi berbeda yang disebut GUID Partition Table (GPT). Karena saya adalah pengguna komputer lama yang masih memakai BIOS, maka saya akan tetap menggunakan partisi berbasis MBR.

Masing-masing sistem operasi memiliki boostrap loader-nya masing-masing. Sebagai contoh, sistem operasi Windows memiliki bootstrap loader yang disebut sebagai NTLDR. Pada Linux, ada beberapa pilihan seperti LILO (Linux Loader) dan GRUB. Hampir semua distro Linux modern sudah memakai GRUB sebagai bootstrap loader. Salah satu kelebihan GRUB adalah ia tidak hanya bisa dipakai untuk men-bootstrap Linux tapi juga sistem operasi lain seperti Windows. GRUB menawarkan banyak fasilitas berguna sehingga boleh dibilang ia adalah sebuah sistem operasi ‘mini’ untuk menjalankan sistem operasi lainnya. Pada artikel ini, saya akan menggunakan GRUB 2 sebagai bootstrap loader pada Live USB yang akan saya buat.

Saya akan mulai dengan melakukan mounting partisi di UFD dengan perintah seperti berikut ini:

# mkdir live_usb
# mount /dev/sdb1 live_usb
# ls live_usb/iso
Fedora-Live-Desktop-x86_64-20-1.iso  systemrescuecd-x86-4.4.0.iso

Partisi pertama pada UFD saya diwakili oleh /dev/sdb1 dan UFD secara keseluruhan diwakili oleh /dev/sdb. Terlihat bahwa partisi pertama saya (bertipe NTFS) hanya berisi 2 file ISO di folder iso.

Sekarang, saya siap untuk men-install UFD pada partisi pertama dengan menggunakan perintah berikut ini:

# grub-install --boot-directory=/mnt/live_usb /dev/sdb
Installing for i386-pc platform.
grub-install: warning: your embedding area is unusually small.  core.img won't fit in it..
grub-install: warning: Embedding is not possible.  GRUB can only be installed in this setup by using blocklists.  However, blocklists are UNRELIABLE and their use is discouraged..
grub-install: error: will not proceed with blocklists.

Mengapa saya mendapatkan pesan kesalahan seperti ini? Kode program yang bisa dikerjakan pertama kali oleh komputer adalah kode program Master Boot Record yang hanya berukuran 512 bytes! Sangat terbatas, bukan? Pada artikel Membuat Bootstraploader Untuk UFD, saya membuat sebuah bootstrap loader sederhana yang muat di MBR. Tapi sistem operasi modern memiliki bootstrap loader yang lebih kompleks. Oleh sebab itu, biasanya bootstrap loader dipisah menjadi dua tahap atau lebih. Kode program 512 bytes pertama akan membaca dan mengerjakan kode program bootstrap loader berikutnya. Pada GRUB 2, kode program tahap kedua ini disebut sebagai core.img. Dimana GRUB 2 meletakkan core.img? Belum ada konsep file system disini sehingga tidak bisa dalam bentuk file! Sebagai alternatifnya, GRUB 2 meletakkan core.img pada sektor setelah MBR dan sebelum boot sector partisi pertama. Saya memperoleh pesan kesalahan di atas karena letak boot sector partisi pertama yang terlalu ‘dempet’ dengan MBR sehingga tidak ada ruang untuk mengisi core.img.

Partisi pada UFD yang saya pakai di-format oleh Windows yang memberi terlalu sedikit jarak. Lalu apa yang harus saya lakukan? Salah satu solusi yang bisa saya tempuh adalah cukup dengan membuat ulang partisi dengan menggunakan GParted. Bila saya membuat partisi dengan GParted, nilai free space preceding (MiB) secara default adalah 1. Ini sudah cukup untuk kebutuhan GRUB 2. Saya kemudian kembali memberikan perintah grub-install seperti berikut ini:

# grub-install --root-directory=/mnt/live_usb /dev/sdb
Installing for i386-pc platform.
Installation finished. No error reported.

Perintah grub-install di atas akan menulis kode program bootstrap loader pada MBR dan sektor lainnya. Saya tidak bisa melihat hasilnya secara langsung tanpa memakai tool yang membaca partisi per sektor. Selain itu, grub-install akan membuat direktori \boot pada partisi pertama. Yang ini terlihat dengan jelas dan merupakan sebuah kelebihan GRUB 2. Mengapa demikian? Ingat kembali bahwa saya menggunakan partisi NTFS yang diciptakan untuk Windows! GRUB 2 bisa membaca dan menulis ke partisi NTFS dengan baik. Hal ini tidak berlaku untuk sebaliknya; Windows tidak mengenali partisi ext4 milik Linux tanpa driver tambahan!

Bila men-boot komputer dari UFD, saya akan memperoleh tampilan seperti pada gambar berikut ini:

Tampilan CLI milik GRUB

Tampilan CLI milik GRUB

Ingat bahwa saya belum men-install sistem operasi apapun pada UFD saya. Yang muncul adalah CLI milik GRUB, bukan BASH atau terminal milik sistem operasi Linux! Membuat bootstrap loader yang punya terminal dan perintah-nya sendiri adalah sebuah usaha yang luar biasa bila dilakukan oleh pihak yang juga membuat sistem operasi. Mengapa demikian? Hal ini karena kode program bootstrap loader akan dibuang dan tidak dipakai lagi begitu kode program sistem operasi berhasil dijalankan! Tentu saja pembuat sistem operasi akan lebih memilih fokus pada kode program sistem operasi ketimbang kode program bootstrap loader yang hanya dipakai sesaat saja. GRUB berbeda karena ia memang dirancang sebagai sebuah bootstrap loader universal yang bisa dipakai pada berbagai situasi tanpa harus terikat pada satu jenis sistem operasi. Oleh sebab itu, banyak pembuat sistem operasi amatir memilih untuk memakai GRUB sebagai bootstrap loader pada sistem operasi yang mereka buat ketimbang menulis kode program bootstrap loader baru.

Menjalankan sistem operasi yang masih berada dalam bentuk file ISO (tanpa di-install) disebut sebagai loopback booting. Hal ini dapat dilakukan dengan menggunakan perintah loopback dari GRUB 2 seperti pada perintah berikut ini:

grub> loopback iso /iso/systemrescuecd-x86-4.4.0.iso

Perintah di atas akan membuat sebuah device baru dengan nama iso yang mewakili isi file ISO (yang saya letakkan pada folder iso). Saya dapat mengakses iso layaknya sebuah partisi, misalnya saya bisa melihat isi direktori di dalam file ISO dengan perintah seperti:

grub> ls (iso)/isolinux/
altker32 altker64 boot.cat chain.32 f1boot.msg f2images.msg f3params.msg f4aru
n.msg f5troubl.msg f6pxe.msg f7net.msg ifcpu64.c32 initram.igz isolinux.bin iso
linux.cfg isolinux.old kbdmap.c32 maps/ membdisk menu.c32 netboot pxelinux.0 reb
oot.c32 rescue32 rescue64 vesamenu.c32

Untuk melakukan loading kernel Linux, saya perlu menggunakan perintah linux seperti berikut ini:

grub> linux (iso)/isolinux/rescue64 isoloop=/iso/systemrescuecd-x86-4.4.0.iso

Pada perintah di atas, saya menambahkan boot parameter isoloop yang berisi lokasi file ISO. Ingat bahwa GRUB sebagai bootstrap loader sama sekali tidak berhubungan dengan kernel Linux. Oleh sebab itu, kernel harus mencari sendiri file ISO yang saya pakai di seluruh partisi yang ada. Setelah itu, ia harus men-mount root (yang mengandung direktori sepeti /etc, dsb) yang ada di ISO. Tidak semua distro Linux mendukung loopback booting! Selain itu, masing-masing kernel juga bisa memiliki nama parameter (boot options) yang berbeda untuk menentukan nama file ISO.

Berikutnya, saya perlu melakukan loading initial ram disk dengan menggunakan perintah initrd seperti berikut ini:

grub> initrd (iso)/isolinux/initram.igz

initrd akan menciptakan partisi baru di memori berdasarkan file yang sudah dipersiapkan oleh pembuat distro Linux. Partisi ini berperan sebagai root awal yang sudah berisi ‘driver’ siap pakai sehingga kernel Linux dapat melakukan inisialisasi dengan baik.

Sebagai langkah terakhir, saya memberikan perintah boot untuk mulai menjalankan Linux:

grub> boot

Tidak lama kemudian, sistem operasi Linux pada distro SystemRescue-Cd berhasil dijalankan langsung dari file ISO tanpa instalasi sama sekali.

Percobaan pertama sukses, lalu bagaimana dengan Fedora? Saya segera me-restart komputer dan memberikan perintah seperti berikut ini di GRUB:

grub> loopback iso /iso/Fedora-Live-Desktop-x86_64-20-1.iso
grub> linux (iso)/isolinux/vmlinuz0 iso-scan/filename=/iso/Fedora-Live-Desktop-x86_64-20-1.iso root=live:CDLABEL=Fedora-Live-Desktop-x86_64-20-1 rd.live.image
grub> initrd (iso)/isolinux/initrd0.img
grub> boot

Tidak seperti pada SystemRescue-cd yang memakai isoloop, boot option sebagai pengenal file ISO di Fedora adalah iso-scan/filename. Selain itu, Fedora juga tidak mendukung root=auto sehingga saya perlu memberikan nilai parameter root untuk men-instruksi-kan kernel agar melakukan mounting root yang ada di file ISO.

Akan tetapi, saya tidak berhasil men-boot Fedora! Saya menemukan pesan kesalahan seperti berikut ini:

dracut-initqueue[331]: mount: unknown filesystem type 'ntfs'
...
Warning: /dev/disk/by-label/Fedora-Live-Desktop-x86_64-20-1 does not exist

Ini adalah contoh kasus dimana bootstrap loader terkadang bisa lebih canggih dari sistem operasi! GRUB bisa membaca partisi NTFS tanpa masalah. Akan tetapi Initrd pada Fedora tidak dilengkapi dengan driver NTFS sehingga ia tidak bisa mencari file ISO yang ada dalam partisi NTFS saya. Ingat bahwa bootstrap loader hanya bertanggung jawab untuk menjalankan kernel! Mereka tidak saling berkomunikasi. GRUB akan ‘mati’ dan ‘lenyap’ setelah Fedora berhasil dijalankan (ini adalah arti kata bootstrap; telur akan hilang setelah ayam muncul!). Jadi, Initrd Fedora bertanggung jawab membaca file ISO di partisi NTFS tanpa bantuan GRUB. Karena ia tidak mampu, maka saya memperoleh pesan kesalahan.

Salah satu solusi untuk permasalahan ini adalah dengan membuat partisi yang dikenali oleh Initrd Fedora. Sebagai contoh, saya membuat partisi baru dengan tipe EXT4 dan meletakkan file ISO pada partisi tersebut. Saya bisa melakukan loopback mounting untuk partisi kedua dengan menggunakan perintah seperti berikut ini:

grub> loopback iso (hd0,2)/Fedora-Live-Desktop-x86_64-20-1.iso
grub> linux (iso)/isolinux/vmlinuz0 iso-scan/filename=/Fedora-Live-Desktop-x86_64-20-1.iso root=live:CDLABEL=Fedora-Live-Desktop-x86_64-20-1 rd.live.image
grub> initrd (iso)/isolinux/initrd0.img
grub> boot

Sekarang, Fedora berhasil di-boot dengan baik melalui ISO tanpa instalasi pada partisi di UFD.

Tentu saja saya tidak ingin selalu memberikan perintah GRUB setiap kali hendak menjalankan distro Linux di Live USB. Untuk itu, saya perlu membuat sebuah file bernama grub.cfg di folder /boot (yang dihasilkan oleh grub-install) yang isinya seperti berikut ini:

menuentry "SystemRescue-Cd 64-bit" {
  loopback iso /iso/systemrescuecd-x86-4.4.0.iso
  linux (iso)/isolinux/rescue64 isoloop=/iso/systemrescuecd-x86-4.4.0.iso
  initrd (iso)/isolinux/initram.igz
}

menuentry "SystemRescue-Cd 32-bit" {
  loopback iso /iso/systemrescuecd-x86-4.4.0.iso
  linux (iso)/isolinux/rescue32 isoloop=/iso/systemrescuecd-x86-4.4.0.iso
  initrd (iso)/isolinux/initram.igz
}

menuentry "Fedora 20 64-bit" {
  loopback iso (hd0,2)/Fedora-Live-Desktop-x86_64-20-1.iso
  linux (iso)/isolinux/vmlinuz0 iso-scan/filename=/Fedora-Live-Desktop-x86_64-20-1.iso root=live:CDLABEL=Fedora-Live-Desktop-x86_64-20-1 rd.live.image
  initrd (iso)/isolinux/initrd0.img
}

Isi dari file grub.cfg adalah perintah-perintah yang sebelumnya sudah saya kerjakan. Kali ini, bila saya melakukan boot dari Live USB, saya tidak akan menemukan CLI GRUB lagi melainkan sebuah menu yang terlihat seperti pada gambar berikut ini:

Tampilan Menu GRUB

Tampilan Menu GRUB

Saya kini memperoleh sebuah Live USB tunggal yang dapat berisi banyak distro Linux. Ini adalah solusi yang lebih baik daripada harus menghabiskan sebuah UFD tunggal untuk setiap file ISO. Selain itu, karena saya meletakkan file ISO pada partisi NTFS, maka saya masih dapat memakai Live USB untuk penyimpanan data di sistem operasi Windows, seperti yang terlihat pada gambar berikut ini:

Partisi tetap dapat dipakai di Windows

Partisi tetap dapat dipakai di Windows

Saya hanya perlu berhati-hati agar tidak menghapus folder boot dan iso secara tidak sengaja.

Memakai DNSCrypt Untuk Menghindari DNS Injection

Selama ini saya tidak pernah memakai DNS server dari ISP, melainkan selalu memakai DNS server dari pihak ketiga. Alasannya adalah DNS server dari ISP sangat lambat dan tidak aman. Semua berjalan dengan lancar sampai ketika beberapa hari yang lalu, saya menemukan banyak masalah pada jaringan saya. Kode program yang menulis log kesalahan ke server chat Slack tiba-tiba mengeluh server tidak ditemukan. Saya juga mulai kesulitan mengakses server GitHub (terkadang sukses tapi terkadang gagal). Seperti masalah jaringan lain pada umumnya, saya mulai melakukan troubleshooting dengan memantau packet melalui Wireshark. Hasilnya, saya menemukan respon dari server milik ISP padahal saya tidak mengaksesnya.

Mengapa demikian? Banyak kemungkinan yang bisa saja terjadi di infrastruktur ISP (atau rahasia lainnya yang tidak saya ketahui karena saya bukan pegawai disitu). ISP bisa saja memakai transparant proxy yang melakukan filtering pada seluruh data yang keluar masuk. Tapi, saya akan mulai dengan memeriksa sesuatu yang lebih masuk akal, misalnya memeriksa DNS aktual yang saya pakai melalui situs https://www.dnsleaktest.com. Saya cukup terkejut ketika mendapati laporan bahwa DNS server yang dipakai adalah DNS server milik ISP. Loh, bukankah saya sudah memakai DNS server milik pihak ketiga? Mungkin sekali ISP telah mengubah setiap request DNS pada port UDP 53 menjadi merujuk ke server DNS milik mereka. Jadi, tidak peduli apapun DNS server yang saya tentukan di router, ISP secara diam-diam akan ‘menggantinya’ ke server DNS yang berbeda. Teknik ini sering kali disebut DNS Injection yang diimplementasikan dengan memakai Transparant DNS Proxy.

Salah satu cara untuk menghindari DNS Injection adalah dengan memakai DNSCrypt yang dikembangkan oleh OpenDNS. DNSCrypt memakai protokol DNSCurve yang ditujukan untuk menggantikan protokol DNS (yang sampai sekarang masih dipakai dimana-mana). Protokol ini lebih aman karena request DNS di-enkripsi sehingga upaya untuk memodifikasi respon dari server akan lebih sulit. Saat ini OpenDNS adalah penyedia terbesar untuk layanan DNS yang mendukung DNSCurve.

Saya segera membuka halaman http://dnscrypt.org/dnscrypt-proxy/download untuk men-download source DNSCrypt. Karena memakai sistem operasi Linux UBuntu, saya men-download file dnscrypt-proxy-1.4.0.tar.gz dan men-extract-nya.

DNSCrypt bergantung pada libsodium. Oleh sebab itu, saya perlu men-download source terbaru library tersebut di https://github.com/jedisct1/libsodium/releases. Setelah men-extract-nya, saya akan men-install library tersebut dengan memberikan perintah berikut ini:

# sudo ./configure
# sudo make
# sudo make install
# sudo ldconfig

Untuk memastikan libsodium telah ter-install, saya dapat memeriksanya dengan perintah berikut ini:

# sudo ldconfig -p | grep libsodium

Berikutnya, saya akan berpindah ke lokasi source code DNSCrypt dan men-install-nya dengan memberikan perintah berikut ini:

# sudo ./configure
# sudo make
# sudo make install

Binary DNSCrypt secara otomatis akan ter-install pada lokasi /usr/local/sbin. Untuk menguji apakah DNSCrypt dapat bekerja dengan baik, saya memberikan perintah berikut ini:

# dnscrypt-proxy --resolver-name=opendns --test=0

Agar lebih aman, saya dapat memakai parameter --user sehingga dnscrypt-proxy melakukan chroot() ke user dengan hak akses yang lebih terbatas. Untuk membuat user baru, saya dapat menggunakan perintah berikut ini:

# sudo adduser --disabled-login --no-create-home --system dnscrypt

Untuk menjalankan DNSCrypt sebagai user baru tersebut, saya dapat menggunakan perintah:

# sudo dnscrypt-proxy --resolver-name=opendns --user=dnscrypt --test=0

Berikutnya, saya ingin DNSCrypt dijalankan secara otomatis. Pada Linux UBuntu yang memakai Upstart, saya bisa menambah file baru di /etc/init dengan nama seperti dnscrypt-proxy.conf yang isinya adalah:

# DNSCrypt Proxy

description "DNSCrypt proxy using OpenDNS resolver"

start on net-device-up

exec /usr/local/sbin/dnscrypt-proxy --resolver-name=opendns --user=dnscrypt

respawn

Penggunaan respawn pada script Upstart di atas menyebabkan DNSCrypt proxy akan dijalankan ulang secara otomatis bila terjadi service tersebut mengalami kegagalan.

Sekarang, bila saya me-restart komputer, DNSCrypt akan tetap dijalankan secara otomatis. Saya bisa memeriksanya dengan menggunakan perintah:

# sudo status dnscrypt-proxy
dnscrypt-proxy start/running, process 199

Setelah DNSCrypt dijalankan, ia akan menyediakan layanan DNS biasa pada port 53. Saya perlu men-konfigurasi jaringan agar tidak lagi mengakses server DNS milik siapapun lagi karena pada akhirnya akan dialihkan ke server milik ISP. Saya perlu men-konfigurasi jaringan agar mengakses DNS di 127.0.0.l (localhost) pada port 53. Setelah menerima request di port ini, DNSCrypt akan melakukan koneksi ter-enkripsi ke port 443 milik server OpenDNS dan mengembalikan hasilnya ke pengguna.

Tapi ada satu masalah yang perlu saya selesaikan terlebih dahulu.

Pada versi Ubuntu baru, saya menemukan bahwa Dnsmasq akan dijalankan secara otomatis. Dnsmasq juga akan memakai port 53 pada 127.0.0.1 sebagai layanan DNS. Untuk itu, saya perlu mematikan layanan Dnsmasq karena tidak dibutuhkan lagi. Caranya adalah dengan mengubah isi file /etc/NetworkManager/NetworkManager.conf dan memberikan komentar pada baris berikut ini:

# dns=dnsmasq

Sebagai langkah terakhir, saya perlu mengatur masing-masing jaringan agar memakai DNS server pada lokasi 127.0.0.1, seperti pada gambar berikut ini:

Mengatur DNS untuk jaringan di Linux Ubuntu

Mengatur DNS untuk jaringan di Linux Ubuntu

Setelah me-restart jaringan, DNS injection dari ISP tidak lagi bekerja. Saya bisa memastikan diri memakai DNS resolver dari OpenDNS dan bukan milik ISP dengan membuka halaman http://www.opendns.com/welcome. Bila memakai DNS server dari OpenDNS, akan terlihat tanda centang besar.

Memakai WMI Event Di PowerShell

Pada WMI, terdapat jenis class yang disebut sebagai event class. Class seperti ini dipakai untuk menandakan bahwa sesuatu telah terjadi. Contohnya adalah class seperti Win32_DeviceChangeEvent, Win32_VolumeChangeEvent, dan sebagainya. Pada artikel ini, saya akan membuat sebuah script PowerShell yang akan mengerjakan aksi tertentu bila pengguna menghubungkan USB Flash Drive (UFD) pada komputer.

Saya dapat menggunakan event class Win32_DeviceChangeEvent untuk memeriksa apakah ada perangkat yang dihubungkan ke komputer. Bila nilai EventType dari Win32_DeviceChangeEvent adalah 2, maka ada perangkat yang dihubungkan ke USB dan bila nilai EventType adalah 3 maka ada perangkat yang dilepaskan dari port USB. Untuk mendaftarkan aksi untuk event class tertentu, saya dapat menggunakan perintah Register-WmiEvent di PowerShell. Sebagai contoh, gambar berikut ini memperlihatkan perintah PowerShell yang akan mencetak tulisan ke layar bila ada perangkat yang dihubungkan ke komputer:

Script yang mencetak tulisan bila ada perangkat yang dihubungkan ke USB

Script yang mencetak tulisan bila ada perangkat yang dihubungkan ke USB

Setelah perintah Register-WmiEvent diberikan, ia akan mendaftarkan sebuah event job yang akan terus bekerja di balik layar. Untuk menghapus atau mematikan event job tersebut, saya menggunakan perintah Remove-Job.

Sekarang, saya akan mengubah aksi pada event job menjadi sesuatu yang lebih rumit dibandingkan sekedar mencetak dengan Write-Host. Sebagai latihan, saya ingin men-copy file bernama virus.txt ke seluruh removable disk di komputer setiap kali ada perangkat USB yang dihubungkan ke komputer. Untuk memperoleh informasi mengenai removable disk di komputer, saya dapat menggunakan perintah PowerShell seperti berikut ini:

PS C:\> (gwmi Win32_LogicalDisk -Filter "DriveType=2").DeviceId

Untuk men-copy file, saya dapat menggunakan perintah Copy-Item atau aliasnya seperti cp dan copy. Sebagai contoh, perintah PowerShell berikut ini akan menghasilkan event job yang akan men-copy file virus.txt ke seluruh removable disk setiap kali ada perangkat USB yang dihubungkan ke komputer:

Script yang men-copy file ke semua removeable drive bila ada perangkat yang dihubungkan ke USB

Script yang men-copy file ke semua removeable drive bila ada perangkat yang dihubungkan ke USB

Salah satu masalah yang timbul pada perintah di atas adalah event job akan dikerjakan berkali-kali (terlihat dari output yang duplikat). Untuk mengatasinya, karena saya hanya tertarik pada penggunaan perangkat yang menyebabkan penambahan drive baru, maka saya dapat menggunakan sebuah turunan dari class Win32_DeviceChangeEvent, yaitu Win32_VolumeChangeEvent. Dengan demikian, saya dapat mengubah perintah yang saya pakai menjadi seperti berikut ini:

PS C:\> Register-WmiEvent -Query "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2" -Action `
>> {
>>   $driveBaru = $Event.SourceEventArgs.NewEvent.DriveName
>>   cp -PassThru C:\virus.txt $driveBaru\virus.txt | `
>>      %{Write-Host "Berhasil men-copy $_"}
>> }
>>

Kali ini, proses pen-copy-an file tidak akan dikerjakan berkali-kali melainkan hanya akan dikerjakan sekali saja.

Agar perintah PowerShell di atas dapat dikerjakan ulang secara mudah, saya akan membuat versi script-nya. Saya dapat menggunakan PowerShell ISE seperti yang terlihat pada gambar berikut ini:

Script yang akan men-copy file ke drive yang baru terdeteksi

Script yang akan men-copy file ke drive yang baru terdeteksi

Saya menyimpan script tersebut dengan nama copy_usb.ps1. Saya juga melakukan sedikit perubahan pada script karena Windows PowerShell ISE memiliki keterbatasan dimana Copy-Item tidak dapat mengenali drive baru (padahal ini bekerja dengan baik bila diberikan secara langsung di shell). Untuk mengatasi hal tersebut, saya mengganti pemanggilan Copy-Item dengan perintah copy dari Command Prompt. Selain itu, saya juga menambahkan loop tak terhingga agar script ini tidak pernah selesai dikerjakan selama sistem operasi belum di-shutdown atau selama tidak terjadi kesalahan. Bila saya ingin script tetap lanjut dikerjakan bila terjadi kesalahan, saya perlu menambahkan -ErrorAction SilentlyContinue pada perintah Register-WmiEvent.

Agar script tersebut dikerjakan secara otomatis setiap kali komputer dinyalakan, saya akan melakukan konfigurasi dengan menjalankan gpedit.msc. Pada tampilan Local Group Policy Editor yang muncul, saya memilih User Configuration, Windows Settings, Scripts (Logon/Logoff), dan men-double click pada Logon. Pada dialog yang muncul, saya memilih tab PowerShell Scripts dan men-klik tombol Add… untuk menambahkan file script yang telah saya buat sebelumnya. Hasilnya akan terlihat seperti pada gambar berikut ini:

Menjalankan PowerShell Scripts secara otomatis

Menjalankan PowerShell Scripts secara otomatis

Sekarang, setiap kali saya menyalakan dan login ke sistem operasi Windows, script PowerShell di atas akan dijalankan secara otomatis, seperti yang terlihat pada tampilan Process Explorer berikut ini:

Script dijalankan setiap kali komputer dinyalakan dan pengguna login

Script dijalankan setiap kali komputer dinyalakan dan pengguna login

PowerShell Yang Lebih Bertenaga Berkat WMI

Pada artikel Apa itu Windows Management Instrumentation (WMI), saya berkenalan dengan teknologi WMI di Windows. WMI Provider menyediakan class berisi property dan method yang dapat dipanggil secara langsung di PowerShell. Memakai class dan objek WMI secara langsung memang merupakan salah satu kelebihan PowerShell yang tidak dimiliki oleh Command Prompt. PowerShell 3 di Windows 8 dilengkapi dengan perintah Get-CimClass yang dapat dipakai untuk mendapatkan informasi mengenai class WMI beserta method dan property yang tersedia di class tersebut seperti pada gambar berikut ini:

PowerShell

PowerShell

Daftar seluruh class yang ada dapat dijumpai di http://msdn.microsoft.com/en-us/library/aa394084.aspx. Secara teknis, yang lebih sering dipakai adalah objek (instance dari class). Untuk memperoleh daftar objek WMI yang ada, saya dapat menggunakan Get-WmiObject seperti pada gambar berikut ini:

PowerShell

PowerShell

Pada artikel ini, saya akan mencoba memakai beberapa objek WMI yang disediakan oleh Windows di PowerShell. Sebagai contoh, untuk melihat informasi BIOS di komputer, saya dapat memeriksa objek Win32_BIOS seperti pada gambar berikut ini:

PowerShell

PowerShell

Pada perintah di atas, saya memakai gwmi yang merupakan alias untuk Get-WmiObject.

Untuk mendapatkan informasi tanggal, saya dapat membaca objek Win32_LocalTime seperti pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai sistem operasi Windows yang sedang aktif, saya dapat membaca informasi dari instance Win32_OperatingSystem seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai komputer, saya dapat membaca property dari instance Win32_ComputerSystem seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai layar, saya dapat membaca property dari instance Win32_VideoController seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai partisi di hard drive, saya dapat membaca property dari instance Win32_DiskPartition seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi software yang ter-install di Windows, saya dapat membaca property dari instance Win32_Product. Selain itu, saya juga dapat memanggil method yang ada untuk melakukan manipulasi software tersebut. Sebagai contoh, saya dapat memanggil method Uninstall untuk menghapus software yang bersangkutan, seperti yang terlihat pada gambar berikut ini:

PowerShell

PowerShell

Untuk mendapatkan informasi mengenai network adapter, saya dapat membaca property dari instance Win32_NetworkAdapter. Selain itu, saya dapat memanggil method Enable() atau Disable() untuk mengaktifkan dan mematikan network adapter yang bersangkutan. Sebagai contoh, saya dapat mematikan atau mengaktifkan seluruh network adapter yang ada dengan perintah seperti pada gambar berikut ini:

PowerShell

PowerShell

Membuat Script Untuk Windows PowerShell

Pada Windows Command Prompt, batch file yang berakhiran .bat adalah script yang berisi kumpulan baris perintah DOS yang akan dikerjakan sebagai satu kesatuan. Windows PowerShell juga memiliki hal serupa dalam bentuk file berakhiran .ps1. Selain itu, Windows juga sudah dilengkapi dengan Windows PowerShell Integrated Scripting Environment (ISE) yang merupakan sebuah GUI untuk menulis script PowerShell. Berikut ini adalah contoh tampilan PowerShell ISE:

Tampilan PowerShell ISE

Tampilan PowerShell ISE

Walaupun sebuah script dapat dijalankan di PowerShell ISE, belum tentu ia dapat dijalankan langsung di PowerShell. Hal ini karena secara default, Windows tidak membolehkan eksekusi script PowerShell, seperti yang terlihat pada contoh berikut ini:

PS C:\> .\latihan.ps1
.\latihan.ps1 : File C:\latihan.ps1 cannot be loaded because running scripts is disabled on this sy
stem. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ .\latihan.ps1
+ ~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

Untuk membolehkan eksekusi script PowerShell secara global, saya perlu menjalankan PowerShell sebagai Administrator dan memberikan perintah seperti:

PS C:\> Set-ExecutionPolicy Unrestricted

Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
http://go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): Y

Windows menyediakan beberapa execution policy seperti Restricted, AllSigned, RemoteSigned, Unrestricted, Bypass, dan Defined. Yang disarankan adalah AllSigned dimana yang boleh dijalankan hanya script PowerShell yang telah di-beri informasi sertifikat digital (dibagian akhir file, dalam bentuk komentar). Bypass adalah execution policy yang paling leluasa.

Selain mengubah execution policy secara global melalui Set-ExecutionPolicy, saya juga dapat menjalankan script dengan memanggil PowerShell dari Command Prompt seperti berikut ini:

C:\> PowerShell -ExecutionPolicy Unrestricted ./latihan.ps1

Seperti apa bentuk script di PowerShell?

Script pada PowerShell mendukung variabel yang selalu diawali dengan tanda dollar seperti $nama dan $proses, seperti yang terlihat pada contoh berikut ini:

$nama = "solid snake"
Write-Host -ForegroundColor Yellow $nama

Sebuah variabel dapat menampung lebih dari satu nilai yang dipisahkan dengan tanda koma. Untuk melakukan looping pada nilai tersebut (array), saya dapat menggunakan foreach seperti pada:

$nama = "solid snake"
$daftarWarna = "Yellow", "Green", "Gray", "Black", "Blue"
foreach($warna in $daftarWarna) {
    Write-Host -ForegroundColor $warna $nama
}

Script di PowerShell bisa menggunakan while, do...while, do...until, for, if, dan switch seperti pada contoh berikut ini:

[int] $i = 1

while ($i -lt 10) {
    "Nilai `$i adalah $i"
    $i++
}

# hasilnya sama dengan:

$i = 1
do {
    "Nilai `$i adalah $i"
    $i++
} until ($i -ge 10)

# hasilnya sama dengan:

foreach($i in 1..9) {
    "Nilai `$i adalah $i"
}

# hasilnya sama seperti:

for($i=1; $i -le 9; $i++) {
    "Nilai `$i adalah $i"
}

Pada ekspresi boolean di script PowerShell, saya melakukan perbandingan dengan operator seperti -eq (sama dengan), -gt (lebih dari), -ge (lebih dari sama dengan), -like, -match dan sebagainya.

Selain itu, PowerShell juga mendukung module, function dan filter. Perbedaan antara function dan filter akan terlihat pada saat melakukan piping. Pada saat melakukan piping ke function, perintah di bagian kiri akan diselesaikan terlebih dahulu baru kemudian function di sebelah kanan dikerjakan. Beda dengan function, filter tidak menunggu hingga perintah di bagian kiri selesai, melainkan dikerjakan untuk setiap iterasi. Sebagai contoh, berikut ini adalah contoh penggunaan filter dalam script untuk menghapus cache Griffon yang sudah tidak ada proyeknya lagi:

Contoh Filter Di PowerShell

Contoh Filter Di PowerShell

Memakai PowerShell Bawaan Windows

Shell berbasis CLI yang sangat terkenal di Windows adalah Command Prompt. Shell yang dulunya dikenal sebagai MS-DOS Prompt ini dapat dipakai untuk mengerjakan perintah DOS seperti dir, cd, del dan sebagainya. DOS sudah lama punah. Oleh sebab itu, Windows juga dilengkapi dengan shell yang lebih canggih yang disebut sebagai Windows PowerShell. Windows 7 sudah dilengkapi dengan PowerShell 2, Windows 8 dilengkapi PowerShell 3 dan Windows 8.1 dilengkapi PowerShell 4. Walaupun demikian, tampaknya popularitas Command Prompt tidak akan dapat digeser oleh PowerShell.

Pada kesempatan ini, saya akan mencoba memakai beberapa perintah PowerShell. Untuk membuka shell tersebut, saya men-klik Start Menu, memilih All Programs, Accessories, Windows PowerShell dan men-klik icon Windows PowerShell. Saya kemudian dapat memberikan perintah PowerShell seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Terlihat bahwa saya dapat memberikan dir dan cd seperti pada Command Prompt. Bedanya, pada PowerShell, segala sesuatunya adalah object (memiliki method dan properties). Perintah yang paling umum yang diberikan di PowerShell berada dalam bentuk cmdlet. Berbeda dengan perintah Command Prompt yang bersifat statis, cmdlet di-implementasi-kan dalam bentuk class .NET yang dapat ditambahkan oleh pengguna atau pihak ketiga.

Perintah dir dan cd yang saya pakai sebenarnya hanya sebuah alias ke cmdlet Get-ChildItem dan Set-Location. Untuk membuktikannya, saya dapat memberikan perintah Get-Alias seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Jadi, perintah dir yang saya berikan sama saja dengan memanggil Cmdlet Get-ChildItem.

Pada Command Prompt, saya dapat men-format output dari dir dengan memberikan argumen seperti dir/w, dir/p dan sebagainya. Lalu bagaimana dengan PowerShell? Saya dapat menggunakan pipe operator yang merujuk ke Cmdlet dengan verb Format seperti Format-Wide seperti yang terlihat pada contoh berikut ini:

Memakai PowerShell

Memakai PowerShell

Sebagai contoh lainnya, saya dapat menggunakan Get-ChildItem untuk menampilkan seluruh file JAR yang ada di direktori dan subdirectori saat ini seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Contoh lainnya, saya dapat menampilkan file JAR yang diurutkan berdasarkan ukuran file dari yang terbesar hingga terkecil, seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Salah satu hal menarik yang saya suka dari PowerShell adalah Cmdlet Out-GridView yang dapat menampilkan object dalam bentuk tabel GUI. Sebagai contoh, saya dapat menampilkan hasil Get-ChildItem dalam bentuk GUI dengan menggunakan perintah seperti yang terlihat pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Selain itu, saya juga dapat mengisi argumen untuk sebuah Cmdlet melalui GUI dengan menggunakan Show-Command seperti pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Segala sesuatu yang diproses dalam PowerShell adalah objek. Untuk melihat property dan method yang dihasilkan, saya dapat menggunakan Get-Member seperti:

PS C:\simple-jpa-demo-inventory> Get-ChildItem | Get-Member


   TypeName: System.IO.DirectoryInfo

Name                      MemberType     Definition
----                      ----------     ----------
Mode                      CodeProperty   System.String Mode{get=Mode;}
Create                    Method         void Create(), void Create(System.Security.AccessControl.DirectorySecurity ...
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreateSubdirectory        Method         System.IO.DirectoryInfo CreateSubdirectory(string path), System.IO.Director...
Delete                    Method         void Delete(), void Delete(bool recursive)
EnumerateDirectories      Method         System.Collections.Generic.IEnumerable[System.IO.DirectoryInfo] EnumerateDi...
EnumerateFiles            Method         System.Collections.Generic.IEnumerable[System.IO.FileInfo] EnumerateFiles()...
EnumerateFileSystemInfos  Method         System.Collections.Generic.IEnumerable[System.IO.FileSystemInfo] EnumerateF...
Equals                    Method         bool Equals(System.Object obj)
GetAccessControl          Method         System.Security.AccessControl.DirectorySecurity GetAccessControl(), System....
GetDirectories            Method         System.IO.DirectoryInfo[] GetDirectories(), System.IO.DirectoryInfo[] GetDi...
GetFiles                  Method         System.IO.FileInfo[] GetFiles(string searchPattern), System.IO.FileInfo[] G...
GetFileSystemInfos        Method         System.IO.FileSystemInfo[] GetFileSystemInfos(string searchPattern), System...
GetHashCode               Method         int GetHashCode()
GetLifetimeService        Method         System.Object GetLifetimeService()
GetObjectData             Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, Sys...
GetType                   Method         type GetType()
...

   TypeName: System.IO.FileInfo

Name                      MemberType     Definition
----                      ----------     ----------
Mode                      CodeProperty   System.String Mode{get=Mode;}
AppendText                Method         System.IO.StreamWriter AppendText()
CopyTo                    Method         System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(s...
Create                    Method         System.IO.FileStream Create()
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreateText                Method         System.IO.StreamWriter CreateText()
Decrypt                   Method         void Decrypt()
Delete                    Method         void Delete()
...

Cmdlet Get-ChildItem pada perintah di atas akan mengembalikan array yang berisi System.IO.DirectoryInfo dan/atau System.IO.FileInfo.

Get-ChildItem tidak hanya dipakai untuk mengakses file system, tapi juga layanan yang ditawarkan oleh provider lainnya dalam bentuk analogi drive dan file. Untuk melihat apa saja provider yang tersedia, saya dapat menggunakan Get-PSProvider seperti pada contoh berikut ini:

PS C:\> Get-PSProvider

Name                 Capabilities                                      Drives
----                 ------------                                      ------
Alias                ShouldProcess                                     {Alias}
Environment          ShouldProcess                                     {Env}
FileSystem           Filter, ShouldProcess, Credentials                {C}
Function             ShouldProcess                                     {Function}
Registry             ShouldProcess, Transactions                       {HKLM, HKCU}
Variable             ShouldProcess                                     {Variable}

Sampai disini, yang saya akses adalah file dan direktori yang disediakan oleh FileSystem provider. Provider yang berbeda akan menawarkan drive yang berbeda. Untuk melihat apa saja yang dapat dipakai sebagai drive, saya dapat menggunakan Get-PSDrive seperti pada contoh berikut ini:

PS C:\simple-jpa-demo-inventory> Get-PSDrive

Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
Alias                                  Alias
C                1165,11        917,46 FileSystem    C:\                                      simple-jpa-demo-inventory
Cert                                   Certificate   \
D                                      FileSystem    D:\
Env                                    Environment
Function                               Function
HKCU                                   Registry      HKEY_CURRENT_USER
HKLM                                   Registry      HKEY_LOCAL_MACHINE
Variable                               Variable
WSMan                                  WSMan

Sebagai contoh, saya dapat membaca isi registry dengan perintah seperti pada gambar berikut ini:

Memakai PowerShell

Memakai PowerShell

Membaca Change Journal File Di NTFS Dengan Visual C++

Kode program untuk artikel ini dapat dijumpai di https://gist.github.com/JockiHendry/9263890.

Pada volume yang memakai file system NTFS, Windows 7 akan mencatat setiap aktifitas perubahan pada file dan directory. Dengan demikian, pengguna bisa tahu apa saja file dimodifikasi di volume/partisi tersebut. Windows akan menyimpan informasi ini di sebuah file bernama \$Extend:$J (file bernama \$Extend di alternate data stream bernama $J). File ini tidak akan penuh karena isinya akan terus ditimpa. Lalu bagaimana cara melihat file ini? Ini adalah salah satu file internal yang dipakai oleh NTFS dan tidak dapat dilihat oleh pengguna (sama seperti file lain seperti $MFT, $MFTMirr, $Boot dan sebagainya). Cara yang paling akurat adalah dengan memeriksa isinya (membaca dari sector). Sebagai alternatifnya, Windows juga menyediakan control code FSCTL_READ_USN_JOURNAL di API DeviceIOControl(). Cara ini lebih mudah karena saya tidak perlu membaca sector secara langsung. Tapi cara ini tidak selalu efektif dalam setiap kondisi karena ia hanya bisa diterapkan di partisi yang sedang di-mount (memiliki drive letter seperti C atau D).

Pada artikel ini saya akan memakai Visual C++ karena API Windows ditulis dan dipublikasikan dengan bahasa C. Memakai bahasa tingkat tinggi seperti .NET dan Java hanya akan menambah repotnya mengurus interoperability. Mahasiswa yang baru belajar sering kali menganggap C dan C++ sudah ‘punah’ digantikan bahasa tingkat tinggi (terutama bila melihat dari kronologi perkembangan bahasa pemograman). Tapi faktanya tidak demikian, developer tetap butuh bahasa yang dekat dengan mesin agar bisa mengendalikan mesin. Mesin komputer tidak bisa membaca HTML dan tidak mengerti OOP; mesin komputer hanya mengerti bit dan byte. Bahasa C tetap merupakan bahasa pilihan saya dalam pemograman low-level hingga saat ini karena ia memang lebih dekat dengan mesin (misalnya memiliki pointer untuk membaca alamat memori secara langsung).

Saya akan mulai dengan membuat sebuah proyek Win32 Console Application di Visual Studio. Karena operasi yang dilakukan oleh program ini membutuhkan hak akses Administrator, maka perlu ada dialog UAC untuk menjalankan program sebagai Administrator (bila belum). Untuk itu saya men-klik kanan nama proyek, memilih Properties. Pada dialog yang muncul, saya memilih Configuration Properties, Linker, Manifest File. Setelah itu, saya mengubah nilai UAC Execution Level menjadi requireAdministrator (/level=’requireAdministrator’).

Langkah pertama yang saya lakukan adalah membuka volume NTFS yang akan dibaca berdasarkan kode hurufnya dengan memanggil Win32 API CreateFile(), seperti yang terlihat pada kode program berikut ini:

#include "stdafx.h"
#include <Windows.h>
#include <WinIoCtl.h>

int _tmain(int argc, _TCHAR* argv[])
{

    // Membaca volume C:
    HANDLE h = CreateFile(L"\.c:", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, OPEN_EXISTING, 0, NULL);
    if (h == INVALID_HANDLE_VALUE) {
        printf("Gagal membaca drive C. Kode Kesalahan: %dn", GetLastError());
        return;
    }

    // Lanjut disini...

    return 0;
}

Setelah itu, saya akan memanggil DeviceIOControl() dengan code berupa FSCTL_QUERY_USN_JOURNAL untuk mendapatkan nilai USN pertama. Saya membutuhkan nilai ini sebagai parameter untuk FSCTL_READ_USN_JOURNAL nanti. Untuk itu, saya menambahkan kode program seperti berikut ini:

...
// Membaca informasi Change Journal dengan FSCTL_QUERY_USN_JOURNAL
USN_JOURNAL_DATA journal;
DWORD jumlahByte;
if (!DeviceIoControl(h, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &journal, sizeof(journal), &jumlahByte, NULL)) {
    printf("Gagal membaca Change Journal.  Kode kesalahan: %dn", GetLastError());
    return -1;
}
printf("USN_JOURNAL_DATA.UsnJournalID    = %#020llxn", journal.UsnJournalID);
printf("USN_JOURNAL_DATA.FirstUsn        = %#020llxn", journal.FirstUsn);
printf("USN_JOURNAL_DATA.NextUsn         = %#020llxn", journal.NextUsn);
printf("USN_JOURNAL_DATA.LowestValidUsn  = %#020llxn", journal.LowestValidUsn);
printf("USN_JOURNAL_DATA.MaxUsn          = %#020llxn", journal.MaxUsn);
printf("USN_JOURNAL_DATA.MaximumSize     = %#020llxn", journal.MaximumSize);
printf("USN_JOURNAL_DATA.AllocationDelta = %#020llxn", journal.AllocationDelta);
...

Pada kode program diatas, saya memakai %llx pada printf() untuk mencetak versi heksadesimal dari nilai __int64 yang juga dikenal sebagai long long (tipe USN adalah alias dari __int64). Bila saya menjalankan kode program di atas, saya akan memperoleh hasil yang sama dengan perintah fsutil usn readdata c:.

Nilai dari journal.FirstUsn adalah record pertama yang dapat dibaca di Change Journal. Oleh sebab itu, saya akan mulai membaca mulai dari record tersebut dengan menggunakan FSCTL_READ_USN_JOURNAL seperti yang terlihat pada kode program berikut ini:

...
// Membaca isi Change Journal
READ_USN_JOURNAL_DATA cariUSN;
CHAR hasil[4096];

cariUSN.StartUsn = journal.FirstUsn;
cariUSN.UsnJournalID = journal.UsnJournalID;
memset(hasil, 0, 4096);
if (!DeviceIoControl(h, FSCTL_READ_USN_JOURNAL, &cariUSN, sizeof(cariUSN), &hasil, 4096, &jumlahByte, NULL)) {
    printf("Gagal membaca record di Change Journal.  Kode kesalahan: %dn", GetLastError());
    return -1;
}

printf("nDaftar Record di Change Journal:nn");

PUSN_RECORD record = (PUSN_RECORD)(((PUCHAR)hasil) + sizeof(USN));
printf("USN        : %#020llxn", record->Usn);
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : %#lxnn", record->Reason);

record = (PUSN_RECORD)(((PCHAR) record) + record->RecordLength);
printf("USN        : %#020llxn", record->Usn); 
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : %#lxnn", record->Reason);
...

Pada kode program di atas, saya menampilkan dua record pertama. Hasil kembalian dari FSCTL_READ_USN_JOURNAL ditampung dalam variabel hasil. Delapan (8) byte pertama dari hasil adalah nilai USN berikutnya setelah USN yang terakhir kali dikembalikan. Ukuran hasil hanya 4096 bytes sehingga ada kemungkinan besar tidak seluruh record tertampung sehingga saya perlu melakukan perulangan dengan kembali memanggil FSCTL_READ_USN_JOURNAL. Selain itu, saya juga harus memperhatikan nilai variabel jumlahByte berisi jumlah byte yang terpakai dari 4096 bytes yang saya sediakan.

Kode program di atas hanya menampilkan dua record saja. Ini tidak begitu berguna! Oleh sebab itu, saya akan menghapus dan menggantinya menjadi sebuah perulangan yang menampilkan seluruh record di Change Journal seperti yang terlihat berikut ini:

...
// Membaca isi Change Journal
printf("nDaftar Record di Change Journal:nn");

READ_USN_JOURNAL_DATA cariUSN;
PUSN_RECORD record;
CHAR hasil[4096];
USN nextUSN;

cariUSN.StartUsn = journal.FirstUsn;
cariUSN.ReasonMask = 0xFFFFFFFF;
cariUSN.ReturnOnlyOnClose = 0;  
cariUSN.BytesToWaitFor = 0;
cariUSN.UsnJournalID = journal.UsnJournalID;

while (1) {
    memset(hasil, 0, 4096);
    if (!DeviceIoControl(h, FSCTL_READ_USN_JOURNAL, &cariUSN, sizeof(cariUSN), &hasil, 4096, &jumlahByte, NULL)) {
        printf("Gagal membaca record di Change Journal.  Kode kesalahan: %dn", GetLastError());
        return -1;
    }           

    record = (PUSN_RECORD)(((PUCHAR)hasil) + sizeof(USN));
    jumlahByte -= sizeof(USN);      

    while (jumlahByte > 0) {
        printf("USN        : %#020llxn", record->Usn);
        printf("Nama File  : %Sn", record->FileName);
        printf("Reason     : %#lxnn", record->Reason);

        jumlahByte -= record->RecordLength;
        record = (PUSN_RECORD)(((PCHAR) record) + record->RecordLength);
    }

    nextUSN = *(USN*) &hasil;       
    if (nextUSN==cariUSN.StartUsn) break;       
    cariUSN.StartUsn = nextUSN;
}
...

Pada kode program di atas, saya selalu memakai sebuah variabel yang sama untuk menampung entry yang dibaca yaitu hasil (sebuah array 4096 bytes). Setiap kali akan membaca di perulangan berikutnya, saya mengosongkan nilai hasil dengan memset(). Dengan demikian, saya dapat membaca Change Journal yang memiliki ukuran besar (dalam satuan GB) tanpa harus takut kehabisan memori. Untuk menentukan kapan harus berhenti, saya memeriksa apakah USN berikutnya yang dikembalikan oleh FSCTL_READ_USN_JOURNAL sama dengan nilai cariUSN.StartUsn yang saya berikan diawal. Bila sama, ini berarti tidak ada lagi yang dapat dibaca dan FSCTL_READ_USN_JOURNAL tidak boleh dipanggil lagi.

Bila saya menjalankan program, saya akan memperoleh banyak output. Alangkah baiknya bila program berhenti setiap kali mencapai batas layar. Oleh sebab itu, saya kemudian menambahkan kode program seperti berikut ini:

...
int _tmain(int argc, _TCHAR* argv[])
{
    // Mengatur console
    CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTitle(L"Change Journal Viewer by TheSolidSnake");   
    ...
    while (1) {
        ...

        while (jumlahByte > 0) {

            ...

            GetConsoleScreenBufferInfo(hStdOut, &screenBufferInfo);                 
            if (screenBufferInfo.dwCursorPosition.Y + 4 > screenBufferInfo.srWindow.Bottom) {                                
                printf("Tekan sembarang tombol untuk melanjutkan...");
                getchar();
                system("cls");
            }
        }

        ...
    }
    return 0;
}
...

Sekarang, hasil dari program sudah mulai bisa dimengerti. Tapi informasi yang ditampilkan rasanya terlalu banyak. Oleh sebab itu, saya akan membatasi hanya menampilkan Reason yang berupa USN_REASON_FILE_CREATE, USN_REASON_FILE_DELETE dan USN_REASON_RENAME_NEW_NAME. Selain itu, saya akan menampilkan Reason yang saat ini dalam bentuk bilangan heksadesimal menjadi tulisan yang lebih mudah dimengerti. Untuk itu, saya melakukan perubahan kode program seperti berikut ini:

...
cariUSN.ReasonMask = USN_REASON_FILE_CREATE | USN_REASON_FILE_DELETE | USN_REASON_RENAME_NEW_NAME;
...
printf("USN        : %#020llxn", record->Usn);
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : ");
if (record->Reason & USN_REASON_CLOSE) {
    printf(" CLOSE");
}
if (record->Reason & USN_REASON_FILE_CREATE) {
    printf(" FILE_CREATE");
}
if (record->Reason & USN_REASON_FILE_DELETE) {
    printf(" FILE_DELETE");
}
if (record->Reason & USN_REASON_RENAME_NEW_NAME) {
    printf(" RENAME_NEW_NAME");
}
printf("nn");
...

Sekarang, bila saya menjalankan program, saya akan memperoleh hasil berupa riwayat file yang dibuat atau dihapus pada volume C: seperti yang terlihat pada gambar berikut ini:

Tampilan program saat dijalankan

Tampilan program saat dijalankan

Isi File Yang Tak Terlihat Di NTFS Alternative Data Stream

Secara fisik, sebuah media penyimpanan seperti hard disk drive (HDD) menyimpan data dalam bentuk byte per byte. Satuan terkecil yang dapat dialokasikan adalah sebuah sector. Pada kebanyakan HDD, 1 sector terdiri atas 512 byte. HDD dengan fitur Advanced Format (AF) memungkinkan 1 sector terdiri atas 4K byte guna meningkatkan efisiensi pada ukuran yang besar. Tapi pengguna tidak menulis dan membaca sector secara langsung. Sistem operasi menawarkan sebuah lapisan abstraksi yang disebut sebagai file. Sebuah file dapat menempati satu atau lebih sector di harddisk. Kenapa tidak langsung menyimpan data ke sector? Karena pengguna awam tidak ingin menghafal nomor sector 🙂 File lebih mudah dipakai karena ia memiliki konsep direktori dan metadata seperti nama dan ukuran.

Tata cara pengelolaan file oleh sistem operasi disebut sebagai file system. Contoh file system yang menjadi legenda di Windows adalah File Allocation Table (FAT). FAT sudah ada sejak Microsoft lahir dimana ia dirancang oleh seorang karyawan pertama di Microsoft. Tidak ada perubahan signifikan pada file system ini selain mengubah pengenal cluster menjadi 32-bit pada FAT32. Walaupun demikian, FAT32 masih populer hingga sekarang, padahal di Windows 7, Microsoft telah menciptakan penerus FAT yang disebut sebagai exFAT (Extended File Allocation Table). exFAT lebih efisien untuk flash drive dan memory card. Tapi penggunaan FAT32 masih bisa dijumpai dengan mudah di media penyimpanan kamera digital, MP3 player murah, dan sebagainya. Lisensi menjadi masalah besar karena perangkat yang memakai exFAT tanpa perjanjian kerja sama dengan Microsoft dapat dituntut 😉 Ini adalah pukulan keras bagi dunia open-source karena exFAT adalah file system ‘rahasia’ dimana orang-orang tidak bisa tahu apa yang sesungguhnya disimpan.

Bila exFAT ditujuan untuk flash drive, maka NTFS (New Technology File System) adalah penerus FAT untuk media penyimpanan permanen seperti HDD. Beruntungnya, NTFS tidak dilindungi oleh paten. Selain itu, hampir semua informasi NTFS sudah di-reverse engineer dan hasilnya telah dipublikasikan secara umum. Ini sebabnya Linux bisa mendukung NTFS secara resmi dalam bentuk driver NTFS-3g. Linux sendiri memiliki file system andalannya yang disebut sebagai ext4.

Jadi, sebuah file adalah kumpulan byte yang mewakili sebuah data yang tersimpan, bukan? Ini adalah pandangan yang normal di UNIX dan turunannya dimana segala sesuatunya adalah file. Tapi pada beberapa sistem operasi lain seperti Windows dan Mac OS, sebuah file dapat terdiri atas satu atau lebih data stream (disebut juga fork). Pada artikel ini, saya akan melihat contoh file yang memiliki lebih dari satu stream di NTFS.

Saya mulai dengan membuat sebuah file bernama latihan.txt secara biasa seperti pada gambar berikut ini:

Membuat file biasa

Membuat file biasa

Sekarang, saya akan menulis ke file yang sama tetapi pada stream yang berbeda seperti yang terlihat pada gambar berikut ini:

Menambahkan isi  file pada alternate data stream

Menambahkan isi file pada alternate data stream

Terlihat bahwa sekarang file latihan.txt memiliki dua isi yang berbeda.

Isi pertama adalah isi di main data stream yang terlihat di perintah dir dan Explorer. Bila saya melihat di Explorer, tetap hanya ada sebuah file dengan ukuran 25 bytes. Bila saya men-double click file tersebut, maka notepad akan muncul dengan isi sesuai pada main data stream. Hal ini menunjukkan bahwa pengguna selalu bekerja pada main data stream pada aktifitas hariannya.

Isi yang kedua adalah isi yang tersimpan di alternate data stream bernama rahasia. Banyak pengguna yang tidak menyadari adanya ‘isi sampingan’ dari sebuah file. Hal ini karena perintah dir, Explorer, dan aplikasi seperti Notepad dan Word selalu bekerja pada main data stream.

Untuk melihat apakah sebuah file memiliki alternate data stream atau tidak, saya dapat menggunakan perintah dir/r seperti yang terlihat pada gambar berikut ini:

Melihat file dengan alternate data stream

Melihat file dengan alternate data stream

Lalu apa gunanya alternate data stream? Salah satu fitur Windows yang aktif menggunakannya adalah Attachment Execution Service. Fitur ini akan menampilkan peringatan keamanan pada saat pengguna menjalankan sebuah program yang di-download dari Internet. Untuk mengenali apakah sebuah file berasal dari Internet, Windows menambahkan sebuah stream bernama Zone.Identifier pada executable file yang di-download.

Saya akan mencoba mensimulasikan perilaku tersebut pada sebuah file exe biasa dengan menambahkan stream Zone.Identifier seperti yang terlihat pada gambar berikut ini:

Menambahkan alternate data stream Zone.Identifier pada file exe

Menambahkan alternate data stream Zone.Identifier pada file exe

Sekarang, bila saya menjalankan file latihan.exe, saya akan memperoleh pesan peringatan keamanan seperti berikut ini:

Windows memakai alternate data stream untuk mengenali file yang di-download dari internet

Windows memakai alternate data stream untuk mengenali file yang di-download dari internet

Sebuah contoh lainnya adalah penggunaan ‘iseng’ adalah dengan menyertakan informasi pengenal pada file exe yang saya sebarkan. Anggap saja saya memiliki file latihan.exe yang hanya saya berikan kepada Perusahaan A, Perusahaan B, dan PerusahaanC. Saya dapat menambahkan informasi lisensi dengan membuat stream lisensi.txt yang berisi nama perusahaan setiap kali mendistribusikan sebuah file latihan.exe seperti yang terlihat pada gambar berikut ini:

Menambah data teks pada file exe dengan alternate data stream

Menambah data teks pada file exe dengan alternate data stream

Bila seseorang men-copy file tersebut ke pihak lain, maka isi dari stream lisensi.txt akan turut di-copy (selama masih memakai NTFS). Dengan demikian, bila terdapat kebocoran, saya dapat memeriksa isi stream lisensi.txt untuk mencari tahu siapa yang membocorkan seperti yang terlihat pada gambar berikut ini:

Membaca data teks pada file exe

Membaca data teks pada file exe

Tentu saja cara ini hanya cara ‘iseng’ yang tidak untuk keperluan serius. Isi dari alternate data stream hanya akan dipertahankan bila file di-copy ke sesama file system NTFS. Bila di-copy ke file system FAT32, maka isi alternate data stream akan hilang. Selain itu, pengguna mahir bisa dengan mudah melihat isi file secara fisik per sector tanpa abstraksi file system.

Membuat Program C# Yang Membaca Data UserAssist

Sistem operasi Windows 7 adalah sebuah sistem operasi yang ‘pintar’. Contohnya, Windows memiliki fitur SuperFetch yang akan menganalisa pola penggunaan program berdasarkan hari dan jam sehingga Windows dapat menebak file-file apa saja yang mungkin akan dipakai pengguna. Berdasarkan informasi tersebut, Windows akan mengisi file ke memori sebelum dipakai sehingga mengurangi kemungkinan page fault. Selain itu, Windows juga terkadang sibuk mengurusi log NTFS di balik layar. Tergantung pada penggunanya, perilaku ‘pintar’ tidak selalu positif. Beberapa pengguna yang ingin punya kendali penuh bisa saja tidak senang dengan Windows yang suka sibuk sendiri tanpa disuruh.

Contoh ‘kepintaran’ Windows 7 yang bisa menuju ke arah berbahaya adalah ia selalu mencatat jumlah eksekusi sebuah program serta kapan sebuah program terakhir kali dijalankan ke lokasi registry HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist. Walaupun program dihapus, catatan ini tetap akan ada selama-lamanya 🙂 Dengan fitur ini, Windows 7 dapat menampilkan program populer yang sering dipakai di menu Start. Masalahnya adalah banyak yang tidak tahu bahwa Windows 7 menyimpan catatan seperti ini. Sepertinya Microsoft berusaha menyembunyikan isi registry tersebut dari tangan jahil karena ia menyamarkan isi registry UserAssist tersebut dengan enkripsi ROT13. Ini adalah jenis enkripsi tidak aman yang sangat mudah dipecahkan; ROT13 hanya melakukan subtitusi huruf seperti A menjadi N, B menjadi O, C menjadi P, dan seterusnya. Walaupun mudah dipecahkan, penggunakan enkripsi ROT13 dan nilai dalam bentuk binary membuat saya sulit mencerna isi dari registry UserAssist dengan mudah.

Oleh sebab itu, saya akan mencoba membuat sebuah program C# yang akan membaca isi registry UserAssist tersebut dan menampilkan informasi dalam bentuk tabel yang lebih mudah dicerna. Program ini adalah sebuah aplikasi WPF yang saya ujikan pada Windows 7 32-bit dan 64-bit. Kode program secara lengkap dapat ditemukan di http://github.com/JockiHendry/ProgramExecutionCounter.

Kode program yang saya buat untuk membaca dari registry terlihat seperti berikut ini:

...
private void OnSearch()
{            
   countEntries.Clear();

   RegistryKey reg = Registry.CurrentUser.OpenSubKey(SelectedSourceType.Key);
   foreach (string valueName in reg.GetValueNames())
   {                
      CountEntry entry = new CountEntry();
      entry.Name = valueName;

      // filter by name
      if (!string.IsNullOrEmpty(NameFilter))
      {
          if (!entry.DecodedName.ToUpper().Contains(NameFilter.ToUpper())) continue;
      }

      entry.Value = (byte[]) reg.GetValue(valueName);
      entry.RegKey = reg.ToString();
      ...
      countEntries.Add(entry);
   }
}  
...      

Pada kode program di atas, saya memakai class Registry dari namespace Microsoft.Win32 untuk membaca isi registry. Saya kemudian melakukan konversi masing-masing value yang saya temukan menjadi sebuah object CountEntry.

Pada class CountEntry, saya menerjemahkan nama yang dienkripsi dengan algoritma ROT13 dengan menggunakan method dictionary lookup. Saya memilih cara ini karena lebih mudah dan jumlah kombinasi yang ada sangat sedikit (A-Z, a-z). Selain itu, nama di registry UserAssist juga mengandung GUID yang mewakili folder spesial di Windows (seperti MyComputer, Program Files, dan sebagainya). Saya bisa memperoleh informasi GUID untuk folder spesial di Windows dengan melihat isi header KnownFolders.h yang terdapat di folder Include di Windows SDK. Setelah membuat variabel static yang berisi dictionary lookup untuk ROT13 dan daftar terjemahan GUID folder, saya kemudian memulai proses dekripsi dengan kode program seperti berikut ini:

...
public String DecodedName
{
    get
    {
        if (string.IsNullOrEmpty(Name))
        {
            return "";
        }
        else
        {
            string result = new string(Name.ToCharArray().Select(c =>
            {
                return lookupTable.ContainsKey(c) ? lookupTable[c] : c;
            }).ToArray());
            foreach (var f in folderGUID)
            {
                if (result.Contains(f.Key)) result = result.Replace(f.Key, f.Value);
            }
            return result;
        }
    }
}
...

Nilai dari setiap key di registry UserAssist adalah deretan byte sebesar 72 byte. Tidak ada yang tahu persis apa saja informasi yang tersimpan, selain Microsoft selaku pencipta Windows (rasa waspada ini tidak perlu ada bila memakai sistem operasi open-source 😉 ). Berdasarkan informasi yang diperoleh dari hasil pencarian Google, saya hanya akan mengambil 4 byte mulai dari posisi ke-4 yang mewakili jumlah eksekusi program dalam bilangan integer 32-bit. Untuk mengubah deretan byte menjadi sebuah int, saya memakai class BitConverter seperti yang terlihat pada kode program berikut ini:

...
public byte[] Value
{
    get
    {
        return this.value;
    }

    set
    {
        this.value = value;
        executionCount = BitConverter.ToInt32(value, 4);
    }
}
...

Tool sederhana ini juga memungkinkan pengguna untuk mengubah jumlah eksekusi secara langsung dari tabel. Untuk menerjemahkan bilangan int yang dimasukkan oleh pengguna menjadi byte array, saya kembali menggunakan class BitConverter dengan memanggil method GetBytes() seperti yang terlihat pada kode program berikut ini:

...
if (e.PropertyName == "ExecutionCount")
{
    try
    {                            
        byte[] newCount = BitConverter.GetBytes(countEntry.ExecutionCount);
        newCount.CopyTo(countEntry.Value, 4);
        Registry.SetValue(entry.RegKey, entry.Name, entry.Value);
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error Updating Registry: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}
...

Pada kode program diatas, saya memanggil method SetValue() dari class Registry untuk menulis perubahan ke registry.

Selain mengubah, tool ini juga memungkinkan pengguna untuk menghapus entry yang dipilihnya. Untuk itu saya perlu memanggil method DeleteValue() dari sebuah RegistryKey untuk menghapus nilai yang dipilih oleh pengguna, seperti yang terlihat pada kode program berikut ini:

public void OnDelete()
{
    if (MessageBox.Show("Do you really want delete this entry?", "Delete Confirmation",
        MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
    {
        RegistryKey key = Registry.CurrentUser.OpenSubKey(RegKey.Replace(@"HKEY_CURRENT_USER", ""), true);
        if (key != null)
        {
            try
            {
                key.DeleteValue(Name);
                PropertyChanged(this, new PropertyChangedEventArgs("DeleteCommand"));
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error Deleting Registry: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

    }
}

Sekarang, saya akan mencoba menjalankan program. Pada saat saya men-klik tombol Search, saya akan memperoleh hasil seperti pada gambar berikut ini:

Menampilkan seluruh entry yang ada

Menampilkan seluruh entry yang ada

Saya dapat langsung mengubah nilai jumlah eksekusi dengan men-double klik pada kolom angka, mengisi angka baru dan menekan tombol Enter, seperti yang terlihat pada gambar berikut ini:

Mengubah nilai registry untuk jumlah eksekusi

Mengubah nilai registry untuk jumlah eksekusi

Selain itu, saya dapat langsung menghapus sebuah entry dengan men-klik icon dengan tanda silang di baris yang bersesuaian, seperti yang terlihat pada gambar berikut ini:

Menghapus nilai dari registry

Menghapus nilai dari registry

Saya juga dapat menampilkan informasi detail untuk sebuah baris dengan men-klik icon kaca pembesar, seperti yang terlihat pada gambar berikut ini:

Menampilkan informasi detail untuk sebuah entry

Menampilkan informasi detail untuk sebuah entry

Lalu, apa manfaat dari tool sendiri ini? Karena registry UserAssist ini akan tetap menyimpan informasi program yang dijalankan walaupun program sudah dihapus atau di-uninstall, maka saya dapat menggunakannya untuk memeriksa apakah pengguna pernah menjalankan sebuah program atau tidak. Fungsi lainnya, misalnya saya dapat memeriksa apa saja program yang pernah dijalankan langsung melalui flash disk (dengan men-klik di Explorer) dengan fasilitas pencarian seperti pada gambar berikut ini:

Melakukan pencarian

Melakukan pencarian

Apakah kali ini ‘kepintaran’ Windows dirasa membuat pengguna khawatir dengan privasi mereka? Beruntungnya, Windows menyediakan fitur untuk mematikan fasilitas ini. Saya dapat menghilangkan pencatatan ini dengan men-klik kanan pada task bar yang kosong, memilih menu Properties. Pada dialog yang muncul, saya memilih tab Start Menu dan menghilangkan tanda centang pada Store and display recently opened programs in the Start menu seperti yang terlihat pada gambar berikut ini:

Menghilangkan pencatatan pada registry UserAssist

Menghilangkan pencatatan pada registry UserAssist