Ship initrd.img in MSI using build-time generation via powershell script (#14424)

* Ship initrd.img in MSI using build-time generation via tar.exe Replace the install-time CreateInitrd/RemoveInitrd custom actions with a build-time step that generates initrd.img using the Windows built-in tar.exe (libarchive/bsdtar) and ships it directly in the MSI. The install-time approach had a race condition: wsl.exe could launch before the CreateInitrd custom action completed, causing ERROR_FILE_NOT_FOUND for initrd.img. Changes: - Add CMake custom command to generate initrd.img via tar.exe --format=newc - Add initrd.img as a regular file in the MSI tools component - Remove CreateInitrd/RemoveInitrd custom actions from WiX, DllMain, and wslinstall.def - Remove CreateCpioInitrd helper and its tests (no longer needed) - Update pipeline build targets to build initramfs instead of init * pr feedback * more pr feedback * switch to using a powershell script instead of tar.exe * powershell script feedback * hopefully final pr feedback --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>

Ben Hillis committed Mar 12, 2026 at 20:42 UTC e5cb458e67b18bb8082f0b79a9f4b3978cf5451b
11 files changed +82 -289
.pipelines/build-stage.yml
+1 -1
@@ -23,7 +23,7 @@ parameters:
23 - name: targets
24 type: object
25 default:
26 - - target: "wsl;libwsl;wslg;wslservice;wslhost;wslrelay;wslinstaller;wslinstall;init;wslserviceproxystub;wslsettings;wslinstallerproxystub;testplugin"
26 + - target: "wsl;libwsl;wslg;wslservice;wslhost;wslrelay;wslinstaller;wslinstall;initramfs;wslserviceproxystub;wslsettings;wslinstallerproxystub;testplugin"
27 pattern: "wsl.exe,libwsl.dll,wslg.exe,wslservice.exe,wslhost.exe,wslrelay.exe,wslinstaller.exe,wslinstall.dll,wslserviceproxystub.dll,wslsettings/wslsettings.dll,wslsettings/wslsettings.exe,wslinstallerproxystub.dll,wsldevicehost.dll,WSLDVCPlugin.dll,testplugin.dll,wsldeps.dll"
28 - target: "msixgluepackage"
29 pattern: "gluepackage.msix"
msipackage/CMakeLists.txt
+2 -2
@@ -22,7 +22,7 @@ foreach(binary ${WINDOWS_BINARIES})
22 list(APPEND BINARIES_DEPENDENCIES "${PACKAGE_INPUT_DIR}/${binary}")
23 endforeach()
24
25 -set(LINUX_BINARIES init)
25 +set(LINUX_BINARIES init;initrd.img)
26 foreach(binary ${LINUX_BINARIES})
27 list(APPEND BINARIES_DEPENDENCIES "${BIN}/${binary}")
28 endforeach()
@@ -52,7 +52,7 @@ add_custom_command(
52
53 add_custom_target(msipackage DEPENDS ${OUTPUT_PACKAGE})
54 set_target_properties(msipackage PROPERTIES EXCLUDE_FROM_ALL FALSE SOURCES ${PACKAGE_WIX_IN})
55 -add_dependencies(msipackage wsl wslg wslservice wslhost wslrelay wslserviceproxystub init wslinstall msixgluepackage)
55 +add_dependencies(msipackage wsl wslg wslservice wslhost wslrelay wslserviceproxystub init initramfs wslinstall msixgluepackage)
56
57 if (WSL_BUILD_WSL_SETTINGS)
58 add_dependencies(msipackage wslsettings libwsl)
msipackage/package.wix.in
+1 -21
@@ -261,6 +261,7 @@
261 <DirectoryRef Id="TOOLSFOLDER">
262 <Component Id="tools" Guid="F0C8D6BA-1502-41E7-BF72-D93DFA134733" Bitness="always64" UninstallWhenSuperseded="yes">
263 <File Id="init" Source="${BIN}/init" Checksum="yes" />
264 + <File Id="initrd.img" Source="${BIN}/initrd.img" Checksum="yes" />
265 <File Id="bsdtar" Source="${BSDTARD_SOURCE_DIR}/${TARGET_PLATFORM}/bsdtar"/>
266
267 <?if "${WSL_DEV_BINARY_PATH}" = "" ?>
@@ -461,21 +462,6 @@
462 Execute="deferred"
463 />
464
464 - <CustomAction Id="CreateInitrd"
465 - Impersonate="no"
466 - BinaryRef="wslinstall.dll"
467 - DllEntry="CreateInitrd"
468 - Return="check"
469 - Execute="deferred"
470 - />
471 -
472 - <CustomAction Id="RemoveInitrd"
473 - Impersonate="no"
474 - BinaryRef="wslinstall.dll"
475 - DllEntry="RemoveInitrd"
476 - Return="check"
477 - Execute="deferred"
478 - />
465
466 <CustomAction Id="RemoveRegistryKeyProtections"
467 Impersonate="no"
@@ -540,16 +526,10 @@
526 <!-- This needs to run after the registry is written because this action needs to read the install path -->
527 <Custom Action="RegisterLspCategories" After="WriteRegistryValues" Condition='((not REMOVE~="ALL") or WIX_UPGRADE_DETECTED) and (not UPGRADINGPRODUCTCODE) and (not SKIPLSP = 1)' />
528
543 - <!-- Generate initrd.img from the init binary whenever the tools component is being installed -->
544 - <Custom Action="CreateInitrd" After="RegisterLspCategories" Condition='$tools=3' />
545 -
529 <!-- This needs to run before the registry is cleared because this action needs to read the install path.
530 This actions runs on uninstallation and upgrade. -->
531 <Custom Action="UnregisterLspCategories" Before="RemoveRegistryValues" Condition='(REMOVE~="ALL") and (not UPGRADINGPRODUCTCODE) and (not SKIPLSP = 1)' />
532
550 - <!-- Remove initrd.img during uninstallation. Must run after StopServices so the file isn't locked -->
551 - <Custom Action="RemoveInitrd" Before="UnregisterLspCategories" Condition='(REMOVE~="ALL") and (not UPGRADINGPRODUCTCODE)' />
552 -
533 <?if "${WSL_BUILD_WSL_SETTINGS}" = "true" ?>
534 <!-- This needs to run before the registry is written because this action calculates registry key names and values -->
535 <Custom Action="CalculateWslSettingsProtocolIds" Before="WriteRegistryValues" Condition='(not REMOVE~="ALL")' />
src/linux/init/CMakeLists.txt
+11
@@ -51,3 +51,14 @@ add_linux_executable(init "${SOURCES}" "${HEADERS};${COMMON_LINUX_HEADERS}" "${I
51 add_dependencies(init localization)
52
53 set_target_properties(init PROPERTIES FOLDER linux)
54 +
55 +set(INITRAMFS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/initrd.img)
56 +set(INIT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}/init)
57 +add_custom_command(
58 + OUTPUT ${INITRAMFS} "${CMAKE_CURRENT_BINARY_DIR}/CmakeFiles/initramfs"
59 + DEPENDS init ${INIT}
60 + COMMAND powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File "${CMAKE_SOURCE_DIR}/tools/create-initrd.ps1" "${INIT}" "${INITRAMFS}"
61 + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/CmakeFiles/initramfs"
62 + VERBATIM)
63 +add_custom_target(initramfs DEPENDS ${INITRAMFS})
64 +set_target_properties(initramfs PROPERTIES FOLDER linux)
src/windows/common/filesystem.cpp
-112
@@ -1115,118 +1115,6 @@ void wsl::windows::common::filesystem::UpdateInit(_In_ PCWSTR BasePath, _In_ ULO
1115 CopyFileWithMetadata(source.c_str(), dest.c_str(), (LX_S_IFREG | 0755), DistroVersion);
1116 }
1117
1118 -void wsl::windows::common::filesystem::CreateCpioInitrd(_In_ const std::filesystem::path& SourcePath, _In_ const std::filesystem::path& DestPath)
1119 -{
1120 - // Open the source init binary
1121 - wil::unique_hfile sourceFile{
1122 - CreateFileW(SourcePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
1123 - THROW_LAST_ERROR_IF(!sourceFile);
1124 -
1125 - // Get file size
1126 - LARGE_INTEGER fileSize{};
1127 - THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(sourceFile.get(), &fileSize));
1128 - THROW_HR_IF(E_INVALIDARG, fileSize.HighPart != 0); // File too large
1129 -
1130 - const DWORD initSize = fileSize.LowPart;
1131 - const auto initDataPadding = (4 - (initSize % 4)) % 4;
1132 - wil::unique_hfile destFile{CreateFileW(DestPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
1133 - THROW_LAST_ERROR_IF(!destFile);
1134 -
1135 - // Clean up the destination file on failure.
1136 - auto deleteFile = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1137 - destFile.reset();
1138 - LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(DestPath.c_str()));
1139 - });
1140 -
1141 - auto writeCpioHeader = [&destFile](DWORD fileSize, PCSTR name) {
1142 - // CPIO newc header: magic(6) + 13 fields of 8 hex chars each = 110 bytes
1143 - constexpr size_t headerSize = 110;
1144 - const auto nameLen = strlen(name) + 1;
1145 - const auto headerPadding = (4 - ((headerSize + nameLen) % 4)) % 4;
1146 - const bool isTrailer = strcmp(name, "TRAILER!!!") == 0;
1147 -
1148 - // Get current time for mtime. CPIO newc format only supports 32-bit fields.
1149 - const auto mtime = static_cast<DWORD>(time(nullptr));
1150 -
1151 - char header[headerSize + 1];
1152 - sprintf_s(
1153 - header,
1154 - "070701" // magic
1155 - "%08X" // inode
1156 - "%08X" // mode
1157 - "%08X" // uid
1158 - "%08X" // gid
1159 - "%08X" // nlink
1160 - "%08X" // mtime
1161 - "%08X" // filesize
1162 - "%08X" // devmajor
1163 - "%08X" // devminor
1164 - "%08X" // rdevmajor
1165 - "%08X" // rdevminor
1166 - "%08X" // namesize
1167 - "%08X", // check
1168 - 0,
1169 - isTrailer ? 0 : 0100755,
1170 - 0,
1171 - 0,
1172 - isTrailer ? 0 : 1,
1173 - mtime,
1174 - fileSize,
1175 - 0,
1176 - 0,
1177 - 0,
1178 - 0,
1179 - static_cast<DWORD>(nameLen),
1180 - 0);
1181 -
1182 - DWORD bytesWritten;
1183 - THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), header, headerSize, &bytesWritten, nullptr));
1184 -
1185 - // Write name with null terminator
1186 - THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), name, static_cast<DWORD>(nameLen), &bytesWritten, nullptr));
1187 -
1188 - // Write padding to align to 4 bytes
1189 - if (headerPadding > 0)
1190 - {
1191 - char padding[4] = {0};
1192 - THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), padding, static_cast<DWORD>(headerPadding), &bytesWritten, nullptr));
1193 - }
1194 - };
1195 -
1196 - // Write init file header
1197 - writeCpioHeader(initSize, SourcePath.filename().string().c_str());
1198 -
1199 - // Copy file contents
1200 - wsl::windows::common::relay::InterruptableRelay(sourceFile.get(), destFile.get(), nullptr, 64 * 1024);
1201 -
1202 - // Write data padding
1203 - if (initDataPadding > 0)
1204 - {
1205 - char padding[4] = {0};
1206 - DWORD bytesWritten;
1207 - THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), padding, static_cast<DWORD>(initDataPadding), &bytesWritten, nullptr));
1208 - }
1209 -
1210 - // Write trailer entry (empty file with name "TRAILER!!!")
1211 - writeCpioHeader(0, "TRAILER!!!");
1212 -
1213 - // Pad the archive to 512-byte boundary
1214 - constexpr DWORD archiveBlockSize = 512;
1215 - LARGE_INTEGER currentPos{};
1216 - THROW_IF_WIN32_BOOL_FALSE(SetFilePointerEx(destFile.get(), {}, &currentPos, FILE_CURRENT));
1217 -
1218 - const auto currentSize = static_cast<ULONGLONG>(currentPos.QuadPart);
1219 - const auto archivePadding = static_cast<DWORD>((archiveBlockSize - (currentSize % archiveBlockSize)) % archiveBlockSize);
1220 - if (archivePadding > 0)
1221 - {
1222 - char paddingBuffer[archiveBlockSize] = {0};
1223 - DWORD bytesWritten;
1224 - THROW_IF_WIN32_BOOL_FALSE(WriteFile(destFile.get(), paddingBuffer, archivePadding, &bytesWritten, nullptr));
1225 - }
1226 -
1227 - deleteFile.release();
1228 -}
1229 -
1118 wil::unique_hfile wsl::windows::common::filesystem::WipeAndOpenDirectory(_In_ LPCWSTR pPath)
1119 {
1120 const auto result = wil::RemoveDirectoryRecursiveNoThrow(pPath);
src/windows/common/filesystem.hpp
-5
@@ -228,11 +228,6 @@ std::wstring UnquotePath(_In_ LPCWSTR Path);
228 /// </summary>
229 void UpdateInit(_In_ PCWSTR BasePath, _In_ ULONG DistroVersion);
230
231 -/// <summary>
232 -/// Creates a CPIO newc format initrd archive from a single source file.
233 -/// </summary>
234 -void CreateCpioInitrd(_In_ const std::filesystem::path& SourcePath, _In_ const std::filesystem::path& DestPath);
235 -
231 /// <summary>
232 /// Wipes out the directory with the given path if it exists, then creates it again and returns
233 /// an open directory handle onto it.
src/windows/wslinstall/DllMain.cpp
-40
@@ -872,46 +872,6 @@ extern "C" UINT __stdcall UnregisterLspCategories(MSIHANDLE install)
872 return NOERROR;
873 }
874
875 -extern "C" UINT __stdcall CreateInitrd(MSIHANDLE install)
876 -try
877 -{
878 - WSL_INSTALL_LOG("CreateInitrd");
879 -
880 - const auto installRoot = wsl::windows::common::wslutil::GetMsiPackagePath();
881 - THROW_HR_IF(E_INVALIDARG, !installRoot.has_value());
882 -
883 - const auto toolsPath = std::filesystem::path(installRoot.value()) / LXSS_TOOLS_DIRECTORY;
884 - const auto initPath = toolsPath / L"init";
885 - const auto initrdPath = toolsPath / LXSS_VM_MODE_INITRD_NAME;
886 - wsl::windows::common::filesystem::CreateCpioInitrd(initPath, initrdPath);
887 -
888 - return NOERROR;
889 -}
890 -catch (...)
891 -{
892 - LOG_CAUGHT_EXCEPTION();
893 -
894 - return ERROR_INSTALL_FAILURE;
895 -}
896 -
897 -extern "C" UINT __stdcall RemoveInitrd(MSIHANDLE install)
898 -{
899 - try
900 - {
901 - WSL_INSTALL_LOG("RemoveInitrd");
902 -
903 - const auto installRoot = wsl::windows::common::wslutil::GetMsiPackagePath();
904 - THROW_HR_IF(E_INVALIDARG, !installRoot.has_value());
905 -
906 - const auto initrdPath = std::filesystem::path(installRoot.value()) / LXSS_TOOLS_DIRECTORY / LXSS_VM_MODE_INITRD_NAME;
907 - THROW_IF_WIN32_BOOL_FALSE(DeleteFileW(initrdPath.c_str()));
908 - }
909 - CATCH_LOG()
910 -
911 - // Failures in this method aren't fatal.
912 - return NOERROR;
913 -}
914 -
875 std::wstring GetWslSettingsInstalledExePath(MSIHANDLE install)
876 {
877 const auto wslSettingsInstallFolder = GetMsiProperty(install, c_wslSettingsInstalledDirectoryPropertyName);
src/windows/wslinstall/wslinstall.def
-2
@@ -4,8 +4,6 @@ EXPORTS
4 CleanExplorerState
5 CleanMsixState
6 DeprovisionMsix
7 - CreateInitrd
8 - RemoveInitrd
7 WslValidateInstallation
8 WslFinalizeInstallation
9 InstallMsix
test/windows/InstallerTests.cpp
-51
@@ -485,57 +485,6 @@ class InstallerTests
485 ValidatePackageInstalledProperly();
486 }
487
488 - TEST_METHOD(InitrdImgLifecycle)
489 - {
490 - // initrd.img is generated during installation from the init binary.
491 - // It should:
492 - // 1. Exist after a fresh install (generated by custom action)
493 - // 2. Be re-created when the component is upgraded
494 - // 3. Persist when reinstalling the same version
495 - // 4. Be removed on uninstall
496 -
497 - auto initrdPath = m_installedPath / L"tools" / L"initrd.img";
498 -
499 - // Uninstall and install an older version to get a clean state
500 - UninstallMsi();
501 - VERIFY_IS_FALSE(IsMsiPackageInstalled());
502 -
503 - // Case 1: Install older version (2.5.10 which ships initrd.img in the MSI)
504 - InstallGitHubRelease(L"2.5.10");
505 - VERIFY_IS_TRUE(IsMsiPackageInstalled());
506 - VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after installing 2.5.10");
507 - ValidatePackageInstalledProperly(L"2.5.10.0");
508 -
509 - // Case 2: Upgrade to current version - initrd.img should be regenerated by custom action
510 - InstallMsi();
511 - VERIFY_IS_TRUE(IsMsiPackageInstalled());
512 - VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after upgrade (generated by installer)");
513 - ValidatePackageInstalledProperly();
514 -
515 - // Case 3: Reinstall same version - initrd.img should persist
516 - InstallMsi();
517 - VERIFY_IS_TRUE(IsMsiPackageInstalled());
518 - VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after reinstalling same version");
519 - ValidatePackageInstalledProperly();
520 -
521 - // Case 4: Downgrade to older version
522 - InstallGitHubRelease(L"2.5.10");
523 - VERIFY_IS_TRUE(IsMsiPackageInstalled());
524 - VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after downgrade");
525 - ValidatePackageInstalledProperly(L"2.5.10.0");
526 -
527 - // Case 5: Uninstall - initrd.img should be removed
528 - UninstallMsi();
529 - VERIFY_IS_FALSE(IsMsiPackageInstalled());
530 - VERIFY_IS_FALSE(std::filesystem::exists(initrdPath), L"initrd.img should be removed on uninstall");
531 -
532 - // Reinstall to leave the system in a consistent state for other tests
533 - InstallMsi();
534 - VERIFY_IS_TRUE(IsMsiPackageInstalled());
535 - VERIFY_IS_TRUE(std::filesystem::exists(initrdPath), L"initrd.img should exist after fresh install");
536 - ValidatePackageInstalledProperly();
537 - }
538 -
488 TEST_METHOD(UpgradeInstallsTheMsiPackage)
489 {
490 // Remove the MSIX package
test/windows/SimpleTests.cpp
-55
@@ -296,60 +296,5 @@ class SimpleTests
296 VERIFY_IS_TRUE(output.find(L"/mnt/c/Program Files (x86)/Common Files") != std::wstring::npos);
297 VERIFY_IS_TRUE(output.find(L"/mnt/c/Users/Test User/AppData/Local/Programs/Microsoft VS Code/bin") != std::wstring::npos);
298 }
299 -
300 - TEST_METHOD(CreateCpioInitrd)
301 - {
302 - using wsl::windows::common::filesystem::CreateCpioInitrd;
303 - using wsl::windows::common::filesystem::TempFile;
304 - using wsl::windows::common::filesystem::TempFileFlags;
305 -
306 - auto validateCpio = [](size_t sourceSize) {
307 - // Create source file with specified size
308 - TempFile sourceFile(GENERIC_WRITE, 0, CREATE_ALWAYS, TempFileFlags::None);
309 - std::vector<char> sourceData(sourceSize, 'X');
310 - DWORD written;
311 - THROW_IF_WIN32_BOOL_FALSE(
312 - WriteFile(sourceFile.Handle.get(), sourceData.data(), static_cast<DWORD>(sourceData.size()), &written, nullptr));
313 - sourceFile.Handle.reset();
314 -
315 - // Create CPIO archive
316 - TempFile destFile(0, 0, CREATE_ALWAYS, TempFileFlags::None, L"img");
317 - CreateCpioInitrd(sourceFile.Path, destFile.Path);
318 -
319 - // Read and validate the CPIO archive
320 - wil::unique_hfile cpioHandle{CreateFileW(
321 - destFile.Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
322 - THROW_LAST_ERROR_IF(!cpioHandle);
323 -
324 - LARGE_INTEGER cpioSize{};
325 - THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(cpioHandle.get(), &cpioSize));
326 - VERIFY_ARE_EQUAL(cpioSize.QuadPart % 512, 0LL); // Archive padded to 512-byte boundary
327 -
328 - char header[111] = {};
329 - DWORD bytesRead;
330 - THROW_IF_WIN32_BOOL_FALSE(ReadFile(cpioHandle.get(), header, 110, &bytesRead, nullptr));
331 - VERIFY_ARE_EQUAL(bytesRead, 110u);
332 -
333 - // Parse CPIO newc header: magic(6) ino mode uid gid nlink mtime filesize devmajor devminor rdevmajor rdevminor namesize check
334 - DWORD fileSize, nameSize;
335 - VERIFY_ARE_EQUAL(sscanf_s(header, "070701%*8x%*8x%*8x%*8x%*8x%*8x%8x%*8x%*8x%*8x%*8x%8x", &fileSize, &nameSize), 2);
336 -
337 - // Verify filename matches source file
338 - auto expectedName = sourceFile.Path.filename().string();
339 - VERIFY_ARE_EQUAL(nameSize, static_cast<DWORD>(expectedName.size() + 1));
340 -
341 - std::string filename(nameSize, '\0');
342 - THROW_IF_WIN32_BOOL_FALSE(ReadFile(cpioHandle.get(), filename.data(), nameSize, &bytesRead, nullptr));
343 - VERIFY_ARE_EQUAL(filename.c_str(), expectedName);
344 -
345 - VERIFY_ARE_EQUAL(fileSize, static_cast<DWORD>(sourceSize));
346 - };
347 -
348 - // Test various sizes to exercise 4-byte alignment padding
349 - for (size_t size : {0, 1, 2, 3, 4, 5, 100, 1024, 4096, 65536})
350 - {
351 - validateCpio(size);
352 - }
353 - }
299 };
300 } // namespace SimpleTests
\ No newline at end of file
tools/create-initrd.ps1 new
+67
@@ -0,0 +1,67 @@
1 +# Copyright (c) Microsoft Corporation.
2 +# Licensed under the MIT License.
3 +
4 +# Creates a CPIO newc format initramfs archive containing a single file named "init"
5 +# with mode 0100755 (rwxr-xr-x), uid 0, gid 0.
6 +
7 +[CmdletBinding()]
8 +param (
9 + [Parameter(Mandatory)][string]$InputFile,
10 + [Parameter(Mandatory)][string]$OutputFile
11 +)
12 +
13 +$ErrorActionPreference = "Stop"
14 +Set-StrictMode -Version Latest
15 +
16 +function Write-Pad([System.IO.Stream]$Stream)
17 +{
18 + $remainder = $Stream.Position % 4
19 + if ($remainder -ne 0)
20 + {
21 + $pad = [byte[]]::new(4 - $remainder)
22 + $Stream.Write($pad, 0, $pad.Length)
23 + }
24 +}
25 +
26 +function Write-CpioEntry([System.IO.Stream]$Stream, [byte[]]$NameBytes, [byte[]]$FileData, [int]$Mode, [uint32]$Mtime)
27 +{
28 + $header = "070701" + # header magic
29 + "00000001" + # inode
30 + ("{0:X8}" -f $Mode) + # mode
31 + "00000000" + # uid
32 + "00000000" + # gid
33 + "00000001" + # nlink
34 + ("{0:X8}" -f $Mtime) + # mtime
35 + ("{0:X8}" -f $FileData.Length) + # filesize
36 + "00000000" + # devmajor
37 + "00000000" + # devminor
38 + "00000000" + # rdevmajor
39 + "00000000" + # rdevminor
40 + ("{0:X8}" -f $NameBytes.Length) + # namesize
41 + "00000000" # check
42 + $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($header)
43 + $Stream.Write($headerBytes, 0, $headerBytes.Length)
44 + $Stream.Write($NameBytes, 0, $NameBytes.Length)
45 + Write-Pad $Stream
46 + if ($FileData.Length -gt 0)
47 + {
48 + $Stream.Write($FileData, 0, $FileData.Length)
49 + Write-Pad $Stream
50 + }
51 +}
52 +
53 +$data = [System.IO.File]::ReadAllBytes($InputFile)
54 +$name = [System.Text.Encoding]::ASCII.GetBytes("init`0")
55 +$mtime = [uint32][System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
56 +
57 +$out = [System.IO.File]::Create($OutputFile)
58 +try
59 +{
60 + Write-CpioEntry $out $name $data 0x81ED $mtime # S_IFREG | 0755
61 + $trailer = [System.Text.Encoding]::ASCII.GetBytes("TRAILER!!!`0")
62 + Write-CpioEntry $out $trailer ([byte[]]::new(0)) 0 0
63 +}
64 +finally
65 +{
66 + $out.Close()
67 +}