2
\$\begingroup\$

My code in full plus the task scheduler definition: https://gist.github.com/DaneWeber/0c5e7978bd3927734173e3afdc3d6338

This is my first non-trivial PowerShell script. This was a pretty major learning experience. I only went down this path once I discovered that xcopy wouldn't be sufficient.

My Ask: What do I not know that I don't know? That is, what about this strikes you as weird, clunky, or "not the PowerShell way"?

Problem I am solving: I want Minecraft worlds sync'ed across two (or more) computers. It doesn't seem like I should have to pay for an additional service to accomplish this.

Other solutions attempted:

  • Symlink/Junction of the Minecraft minecraftWorlds folder with one in OneDrive. Minecraft seemed to be unable/unwilling to see the data.
  • xcopy /d to update files. The problem is that Minecraft renames the files every save, so each save results in another large file, growing out of control.

Solution approach:

  • If any world exists in one location (local or cloud) but not the other: copy it.
  • If one location's world contains a file newer than all other files in the other location, replace the entire world with the newer one. (You have to be careful not to consider folders, since they have a modified date of their creation, even when you copy a folder.)
  • In case the script runs while the cloud service is still downloading data, copy files from the cloud if there are ones missing locally.

The script:

$minecraftSaveFolder = "minecraftWorlds"
$minecraftLocalPath = $env:LOCALAPPDATA + "\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang"
$minecraftLocalSave = $minecraftLocalPath + "\" + $minecraftSaveFolder
$minecraftCloudSave = $PSScriptRoot + "\MinecraftBackup"

$cloudWorlds = Get-ChildItem $minecraftCloudSave
$localWorlds = Get-ChildItem $minecraftLocalSave

$saves = Compare-Object $cloudWorlds $localWorlds -IncludeEqual

switch ($saves) {
  ( {$PSItem.SideIndicator -eq "<="}) {
    Write-Output "Copying from cloud: $($PSItem.InputObject.Name)"
    Copy-Item -Path $PSItem.InputObject.FullName -Destination $minecraftLocalSave -Container -Recurse
  }
  ( {$PSItem.SideIndicator -eq "=>"}) {
    Write-Output "Copying to cloud: $($PSItem.InputObject.Name)"
    Copy-Item -Path $PSItem.InputObject.FullName -Destination $minecraftCloudSave -Container -Recurse
  }
  ( {$PSItem.SideIndicator -eq "=="}) {
    $cloudLatest = (( Get-ChildItem ( $minecraftCloudSave + "\" + $PSItem.InputObject) -Recurse -File ).LastWriteTime | Measure-Object -Maximum)
    $localLatest = (( Get-ChildItem ( $minecraftLocalSave + "\" + $PSItem.InputObject) -Recurse -File ).LastWriteTime | Measure-Object -Maximum)
    if ($cloudLatest.Maximum -eq $localLatest.Maximum) {
      if ($cloudLatest.Count -gt $localLatest.Count) {
        Write-Output "Adding files from cloud: $($PSItem.InputObject.Name)"
        xcopy ($minecraftCloudSave + "\" + $PSItem.InputObject) $minecraftLocalSave /d /e /c /i /h /y
      }
      else {
        Write-Output "Already synchronized: $($PSItem.InputObject.Name)"
      }
    }
    elseif ($cloudLatest.Maximum -gt $localLatest.Maximum) {
      $world = $PSItem.InputObject.Name
      Write-Output "Over-writing local with $world"
      $old = Rename-Item -Path "$minecraftLocalSave\$world" -NewName ($world + "_old") -PassThru
      Copy-Item -Path "$minecraftCloudSave\$world" -Destination $minecraftLocalSave -Container -Recurse
      Remove-Item -Recurse -Force -Path $old
    }
    elseif ($cloudLatest.Maximum -lt $localLatest.Maximum) {
      $world = $PSItem.InputObject.Name
      Write-Output "Updating cloud with $world"
      $old = Rename-Item -Path "$minecraftCloudSave\$world" -NewName ($world + "_old") -PassThru
      Copy-Item -Path "$minecraftLocalSave\$world" -Destination $minecraftCloudSave -Container -Recurse
      Remove-Item -Recurse -Force -Path $old
    }
    else {
      Write-Error "Something went wrong with the folder comparison."
    }
  }
}
\$\endgroup\$
0
\$\begingroup\$

Looks fine for me. As a note - instead of using Write-Output for display purposes use Write-Host cmdlet.

In a nutshell, Write-Host writes to the console itself. Think of it as a MsgBox in VBScript. Write-Output, on the other hand, writes to the pipeline, so the next command can accept it as its input. You are not required to use Write-Output in order to write objects, as Write-Output is implicitly called for you.

\$\endgroup\$
  • \$\begingroup\$ Hi Kirill, you should explain why using Write-Host is a better solution as this is pretty much the goal of CodeReview. \$\endgroup\$ – IEatBagels Sep 25 '18 at 13:28
  • \$\begingroup\$ @IEatBagels, fair enough \$\endgroup\$ – Kirill Pashkov Sep 25 '18 at 13:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.