How to Set Up Storage Spaces on Windows Server 2012 R2

Storage Spaces is a software-defined storage technology built into Windows Server 2012 R2 that allows you to pool physical disks of varying sizes and types (SAS, SATA, SSD) into virtual storage pools, then carve those pools into virtual disks with resiliency options such as mirroring, parity, or simple (no redundancy). Storage Spaces provides enterprise-grade features — thin provisioning, tiered storage mixing SSDs and HDDs, write-back cache — without requiring a dedicated SAN.

Note: This is the standalone Storage Spaces feature available on Windows Server 2012 R2. Storage Spaces Direct (S2D), which enables all-flash or hybrid hyperconverged storage over RDMA networks, was introduced in Windows Server 2016 and is NOT available on Server 2012 R2.

Prerequisites

  • Windows Server 2012 R2 (Standard or Datacenter).
  • Minimum two physical disks for mirroring; three or more for parity. Disks should have no existing partitions.
  • Disks should be identical models when possible for best performance (though mixed sizes/types are supported).
  • Administrator account.
  • For tiered storage: at least one SSD and one HDD in the pool.

Step 1: Identify Available Disks

# List all physical disks
Get-PhysicalDisk | Select-Object FriendlyName, MediaType, Size, HealthStatus, OperationalStatus

# List disks that can be added to a storage pool (must not be used by the OS)
Get-PhysicalDisk -CanPool $true | 
    Select-Object FriendlyName, MediaType, Size, BusType | Format-Table

# Check if any disks are currently in storage pools
Get-PhysicalDisk | Select-Object FriendlyName, StoragePoolFriendlyName

# Get disk bus type information (important for tiering — SAS vs SATA vs NVMe)
Get-PhysicalDisk | Select-Object FriendlyName, BusType, MediaType, Size | Format-Table

Step 2: Create a Storage Pool

# Get a reference to all available physical disks for pooling
$poolDisks = Get-PhysicalDisk -CanPool $true

# Create the storage pool
New-StoragePool `
    -FriendlyName "DataPool01" `
    -StorageSubSystemFriendlyName "Storage Spaces*" `
    -PhysicalDisks $poolDisks `
    -ResiliencySettingNameDefault Mirror `
    -ProvisioningTypeDefault Thin

# Verify the pool was created
Get-StoragePool -FriendlyName "DataPool01" | 
    Select-Object FriendlyName, Size, AllocatedSize, HealthStatus, OperationalStatus

# List physical disks in the pool
Get-StoragePool -FriendlyName "DataPool01" | Get-PhysicalDisk | 
    Select-Object FriendlyName, Size, MediaType

Step 3: Create Virtual Disks with Resiliency

# Create a two-way mirror virtual disk (data written to 2 copies)
# Requires at least 2 physical disks in the pool
New-VirtualDisk `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "MirroredDisk01" `
    -ResiliencySettingName Mirror `
    -NumberOfDataCopies 2 `
    -Size 500GB `
    -ProvisioningType Thin

# Create a three-way mirror virtual disk (survives loss of 2 disks)
# Requires at least 5 physical disks
New-VirtualDisk `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "HighResilient01" `
    -ResiliencySettingName Mirror `
    -NumberOfDataCopies 3 `
    -Size 200GB

# Create a parity virtual disk (RAID-5 equivalent)
# Requires at least 3 physical disks; writes are slower than mirror
New-VirtualDisk `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "ParityDisk01" `
    -ResiliencySettingName Parity `
    -Size 1TB `
    -ProvisioningType Thin

# Create a simple virtual disk (no redundancy — maximum performance/capacity)
# Not recommended for production data
New-VirtualDisk `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "SimpleDisk01" `
    -ResiliencySettingName Simple `
    -Size 100GB

# List all virtual disks
Get-VirtualDisk | Select-Object FriendlyName, ResiliencySettingName, Size, HealthStatus

Step 4: Initialize, Partition, and Format Virtual Disks

# After creating the virtual disk, initialize it as a regular disk
$vDisk = Get-VirtualDisk -FriendlyName "MirroredDisk01" | Get-Disk

# Initialize with GPT partition table
Initialize-Disk -Number $vDisk.Number -PartitionStyle GPT -PassThru

# Create a partition and format it
New-Partition -DiskNumber $vDisk.Number -UseMaximumSize -DriveLetter E |
    Format-Volume -FileSystem NTFS -NewFileSystemLabel "MirroredData01" -Confirm:$false

# Combine steps in a pipeline
Get-VirtualDisk -FriendlyName "ParityDisk01" |
    Get-Disk |
    Initialize-Disk -PartitionStyle GPT -PassThru |
    New-Partition -DriveLetter F -UseMaximumSize |
    Format-Volume -FileSystem NTFS -NewFileSystemLabel "ParityData01" -Confirm:$false

# Verify volumes
Get-Volume | Where-Object { $_.DriveLetter -in "E","F" } | 
    Select-Object DriveLetter, FileSystemLabel, SizeRemaining, Size

Step 5: Configure Storage Tiering

Storage tiering automatically moves frequently accessed (hot) data to SSDs and less-accessed (cold) data to HDDs within the same virtual disk, delivering SSD performance for hot data at HDD cost-per-gigabyte.

# First, ensure the pool contains both SSD and HDD media types
Get-StoragePool -FriendlyName "DataPool01" | Get-PhysicalDisk | 
    Select-Object FriendlyName, MediaType, Size

# Create storage tiers in the pool
$ssdTier = New-StorageTier `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "SSD-Tier" `
    -MediaType SSD `
    -ResiliencySettingName Mirror

$hddTier = New-StorageTier `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "HDD-Tier" `
    -MediaType HDD `
    -ResiliencySettingName Mirror

# Create a tiered virtual disk
New-VirtualDisk `
    -StoragePoolFriendlyName "DataPool01" `
    -FriendlyName "TieredVolume01" `
    -StorageTiers $ssdTier, $hddTier `
    -StorageTierSizes 50GB, 500GB `
    -WriteCacheSize 10GB `
    -ResiliencySettingName Mirror

# Enable the Storage Tiers Optimization scheduled task
Enable-ScheduledTask -TaskName "MicrosoftWindowsStorage Tiers ManagementStorage Tiers Optimization"

# Run optimization immediately
Optimize-Volume -DriveLetter G -TierOptimize

Step 6: Monitor and Manage Storage Spaces

# Check storage pool health
Get-StoragePool | Select-Object FriendlyName, HealthStatus, OperationalStatus, Size, AllocatedSize

# Check all virtual disk health
Get-VirtualDisk | Select-Object FriendlyName, HealthStatus, OperationalStatus, ResiliencySettingName

# Check physical disk health in the pool
Get-StoragePool -FriendlyName "DataPool01" | Get-PhysicalDisk | 
    Select-Object FriendlyName, HealthStatus, OperationalStatus, Size

# Add new physical disks to an existing pool
$newDisks = Get-PhysicalDisk -CanPool $true
Add-PhysicalDisk -StoragePoolFriendlyName "DataPool01" -PhysicalDisks $newDisks

# Replace a failed disk
# First, identify the failed disk
Get-PhysicalDisk | Where-Object { $_.HealthStatus -eq "Unhealthy" }

# Retire the failed disk
Set-PhysicalDisk -FriendlyName "PhysicalDisk5" -Usage Retired

# Add the replacement disk to the pool
$newDisk = Get-PhysicalDisk -CanPool $true | Select-Object -First 1
Add-PhysicalDisk -StoragePoolFriendlyName "DataPool01" -PhysicalDisks $newDisk

# Remove the failed/retired disk
Remove-PhysicalDisk -StoragePoolFriendlyName "DataPool01" -PhysicalDisks (Get-PhysicalDisk -FriendlyName "PhysicalDisk5")

Summary

Storage Spaces on Windows Server 2012 R2 provides a flexible, software-defined storage platform that turns commodity disks into resilient, manageable storage volumes. The key configuration decisions are: choose Mirror resiliency for performance-sensitive or critical data, use Parity for bulk cold storage where capacity efficiency matters more than write performance, enable storage tiering when the pool contains both SSDs and HDDs to get the best of both media types, and always size the pool with enough physical disks to support the chosen resiliency level plus spares. Monitor pool and virtual disk health proactively — Storage Spaces tolerates disk failures within the resiliency limits, but a second disk failure during a degraded rebuild can cause data loss, so replacing failed disks promptly is critical.