diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ad0fabe..81f812d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,7 +16,7 @@ jobs:
       - name: Checkout source
         uses: actions/checkout@v2
 
-      - uses: Swatinem/rust-cargo@v1
+      - uses: Swatinem/rust-cache@v1
 
       - name: cargo test
         uses: actions-rs/cargo@v1
diff --git a/Cargo.lock b/Cargo.lock
index 60e8adf..95cb5c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6,9 +6,65 @@ version = 3
 name = "CreateProcessW"
 version = "0.1.0"
 dependencies = [
+ "thiserror",
  "windows",
 ]
 
+[[package]]
+name = "proc-macro2"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
 [[package]]
 name = "windows"
 version = "0.24.0"
diff --git a/Cargo.toml b/Cargo.toml
index 44f61d0..bd2368e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,9 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
+[dependencies]
+thiserror = "1.0.30"
+
 [dependencies.windows]
 version = "0.24.0"
 features = [
diff --git a/src/lib.rs b/src/lib.rs
index 44d8a23..b1fa0d0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1,199 @@
+// Disable warning for the crate name, not a really good way to do this but..
+// (https://github.com/rust-lang/rust/issues/45127)
 #![allow(non_snake_case)]
+
+use std::ffi::{c_void, OsStr, OsString};
+use std::mem::size_of;
+use std::path::{Path, PathBuf};
+use thiserror::Error;
+use windows::Win32::Foundation::{CloseHandle, GetLastError, PWSTR, STATUS_PENDING};
+use windows::Win32::Security::SECURITY_ATTRIBUTES;
+use windows::Win32::System::Threading::{
+    GetExitCodeProcess, TerminateProcess, WaitForSingleObject, PROCESS_CREATION_FLAGS,
+    PROCESS_INFORMATION, STARTUPINFOW, WAIT_OBJECT_0,
+};
+use windows::Win32::System::WindowsProgramming::INFINITE;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("cannot create process: {0}")]
+    CreationFailed(u32),
+    #[error("cannot get exit status: {0}")]
+    GetExitCodeFailed(u32),
+    #[error("cannot kill process: {0}")]
+    KillFailed(u32),
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug)]
+pub struct Command {
+    command: OsString,
+    inherit_handles: bool,
+    current_directory: Option<PathBuf>,
+}
+
+impl Command {
+    pub fn new(command: impl Into<OsString>) -> Self {
+        Self {
+            command: command.into(),
+            inherit_handles: true,
+            current_directory: None,
+        }
+    }
+
+    pub fn inherit_handles(&mut self, inherit: bool) -> &mut Self {
+        self.inherit_handles = inherit;
+        self
+    }
+
+    pub fn current_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
+        self.current_directory = Some(dir.into());
+        self
+    }
+
+    pub fn spawn(&mut self) -> Result<Child> {
+        Child::new(
+            &self.command,
+            self.inherit_handles,
+            self.current_directory.as_deref(),
+        )
+    }
+
+    pub fn status(&mut self) -> Result<ExitStatus> {
+        self.spawn()?.wait()
+    }
+}
+
+#[derive(Debug)]
+pub struct Child {
+    process_information: PROCESS_INFORMATION,
+}
+
+impl Child {
+    fn new(
+        command: &OsStr,
+        inherit_handles: bool,
+        current_directory: Option<&Path>,
+    ) -> Result<Self> {
+        let mut startup_info = STARTUPINFOW::default();
+        let mut process_info = PROCESS_INFORMATION::default();
+
+        startup_info.cb = size_of::<STARTUPINFOW>() as u32;
+
+        let process_creation_flags = PROCESS_CREATION_FLAGS(0);
+
+        let res = unsafe {
+            if let Some(directory) = current_directory {
+                let directory = directory.as_os_str();
+                windows::Win32::System::Threading::CreateProcessW(
+                    PWSTR::default(),
+                    command,
+                    std::ptr::null() as *const SECURITY_ATTRIBUTES,
+                    std::ptr::null() as *const SECURITY_ATTRIBUTES,
+                    inherit_handles,
+                    process_creation_flags,
+                    std::ptr::null() as *const c_void,
+                    directory,
+                    &startup_info,
+                    &mut process_info as *mut PROCESS_INFORMATION,
+                )
+            } else {
+                windows::Win32::System::Threading::CreateProcessW(
+                    PWSTR::default(),
+                    command,
+                    std::ptr::null() as *const SECURITY_ATTRIBUTES,
+                    std::ptr::null() as *const SECURITY_ATTRIBUTES,
+                    inherit_handles,
+                    process_creation_flags,
+                    std::ptr::null() as *const c_void,
+                    PWSTR::default(),
+                    &startup_info,
+                    &mut process_info as *mut PROCESS_INFORMATION,
+                )
+            }
+        };
+
+        if res.as_bool() {
+            Ok(Self {
+                process_information: process_info,
+            })
+        } else {
+            Err(Error::CreationFailed(unsafe { GetLastError().0 }))
+        }
+    }
+
+    pub fn wait(&self) -> Result<ExitStatus> {
+        unsafe {
+            let mut exit_code: u32 = 0;
+            let res = WaitForSingleObject(self.process_information.hProcess, INFINITE);
+
+            if res == WAIT_OBJECT_0 {
+                if GetExitCodeProcess(
+                    self.process_information.hProcess,
+                    &mut exit_code as *mut u32,
+                )
+                .as_bool()
+                {
+                    close_handles(&self.process_information);
+                    Ok(ExitStatus(exit_code))
+                } else {
+                    Err(Error::GetExitCodeFailed(GetLastError().0))
+                }
+            } else {
+                Err(Error::GetExitCodeFailed(GetLastError().0))
+            }
+        }
+    }
+
+    pub fn try_wait(&self) -> Result<Option<ExitStatus>> {
+        unsafe {
+            let mut exit_code: u32 = 0;
+
+            let res = GetExitCodeProcess(
+                self.process_information.hProcess,
+                &mut exit_code as *mut u32,
+            );
+
+            if res.as_bool() {
+                if exit_code == STATUS_PENDING.0 {
+                    Ok(None)
+                } else {
+                    close_handles(&self.process_information);
+                    Ok(Some(ExitStatus(exit_code)))
+                }
+            } else {
+                Err(Error::GetExitCodeFailed(GetLastError().0))
+            }
+        }
+    }
+
+    pub fn kill(&self) -> Result<()> {
+        unsafe {
+            let res = TerminateProcess(self.process_information.hProcess, 0);
+
+            if res.as_bool() {
+                Ok(())
+            } else {
+                Err(Error::KillFailed(GetLastError().0))
+            }
+        }
+    }
+}
+
+pub struct ExitStatus(u32);
+
+impl ExitStatus {
+    pub fn success(&self) -> bool {
+        self.0 == 0
+    }
+
+    pub fn code(&self) -> u32 {
+        self.0
+    }
+}
+
+unsafe fn close_handles(process_info: &PROCESS_INFORMATION) {
+    CloseHandle(process_info.hProcess);
+    CloseHandle(process_info.hThread);
+}