<# Eldradia Project Zomboid - client mod sync check GENERATED 2026-08-18 22:32 from the server's own mod list. Do not hand-edit; ask Michael for a fresh copy instead. What this does: 1. Finds your Steam Workshop folder and your Zomboid folder. 2. Compares what you have against what the server actually loads. 3. Tells you exactly what is missing, and opens those Workshop pages. 4. Writes the server's mod preset so you can paste it into the Mods menu. What it does NOT do: subscribe for you. Steam provides no supported way to do that, and the unsupported way risks your account. You click Subscribe; this script makes sure you click the right things and nothing else. Nothing here modifies your game. It only reads, plus writes one .txt file to your Desktop. #> [CmdletBinding()] param( [switch]$OpenMissing, # open a Workshop page for each missing item [int]$OpenLimit = 25, # safety cap so a fresh install does not open 282 tabs [string]$WorkshopDir # override if Steam is somewhere this cannot find ) $ErrorActionPreference = 'Stop' $AppId = '108600' $Required = @('2999183635','2900580391','2772575623','3005903549','3041122351','3566868353','3447272250','2478247379','3026723485','3258343790','2991201484','3631989559','3756938756','2937786633','3766571591','2873290424','3670063857','2913633066','3642935062','3490370700','3743371090','3213391371','3161951724','3730833846','3346905070','2799152995','3726526329','3703948448','3248388837','3253385114','2886832257','2618213077','3320947974','3379334330','3596903773','3592777775','3684254299','3409287192','2805630347','3601417745','3418252689','3413704851','3614034284','3418253716','3413706334','3428008364','2870394916','2566953935','3226885926','3196180339','3110911330','3052360250','2886832936','3435796523','3034636011','2886833398','2932549988','3570973322','3292659291','3110913021','2952802178','2942793445','3366300557','3539691958','3008795514','3770890864','3504401781','2409333430','2642541073','2962175696','3287727378','2846036306','3152529790','2969343830','3073430075','3001592312','3088951320','2932547723','2971246021','3610677934','3722134990','3722064198','3391902125','3716934959','2584112711','3162566044','2840805724','3387957272','3432006285','3416833689','3417502641','3418061837','2714198296','3772979228','3749026793','3719237374','3650168851','3454414190','3477237184','3569718104','3386906181','3402516345','3543229299','2804531012','3453676250','3307376332','3411888105','3431256608','3475347500','3635394848','3736813592','3538760023','3299457564','3521263771','3457969588','3443982326','3766508989','3610005735','2788256295','1962761540','3660235667','3389448389','3388183573','3635856965','3765241705','3394044313','3670064951','3393080905','3711044244','3580513066','3437629766','3725497089','2625625421','3428715523','3675740871','3417051694','3447775367','3671176591','3755185651','3674800555','3453369692','3455296935','2896041179','3652008781','3153010942','2447729538','3042138819','3282215361','3755993986','2544353492','3567695139','3618557184','3626823538','3653092321','3426448380','3641048285','3717902500','2868828908','3470422050','3558422176','2553809727','3688879406','3629835761','2926455909','3665548194','3412944806','3403870858','3774789651','3360392628','3710913197','3423660713','3077900375','3396446795','3395171770','3520758551','3413150945','3533994661','3101668503','3536052310','3502080466','3540503606','3508537032','3760534563','3438126404','3760471726','3765077081','3765079521','3765670104','3751364201','3739256725','3386486141','3465040406','3422220305','3739173520','3725311427','3645980077','3490188370','3391323469','2956146279','3656190498','3635591071','2699828474','2981090458','2142622992','3684808606','3409143790','3734639991','3399320470','3596827035','3402171609','2503622437','3290232938','3669451917','3582960654','3772052709','3414634809','2684285534','2463184726','2812326159','3579640010','2366717227','3677017550','3680633169','3687394815','3389605231','3171167894','2769706949','3429790870','3431734923','3572122233','3330403100','3615422211','3625874308','3389609367','3577903007','3401134276','3423424077','2290459371','3436499337','2757712197','2831786301','3413237147','3423725989','3408186716','3460812542','3408740647','3717099183','2934155949','3624971238','3615135168','3155647374','3769335201','3557111695','3488113291','3660382016','3774841057','3570485296','3692043319','3051276857','3593649462','3444499190','3768745256','3390760692','2686624983','3457064550','3701820916','3692027888','3662387304','3702832442','3683880951','3570250507','3232993626','3677910297','2957935793','3776534799','3650112103','3776531193','3713977259','2883755057','3654864424','3774828917','3778868211','3781616141','3783535220','3700831155','3779561845','3778511497','3378285185') $Preset = 'Eldradia-Sophie:StarlitLibrary;TchernoLib;ModLoadOrderSorter_b42;damnlib;YAPZLib;ChuckleberryFinnAlertSystem;errorMagnifier;ZomboidPaperAPI;MoodleFramework;moodlesinlua;NeatUI_Framework;HBVCEFb42;KATTAJ1_ClothesCore;SpnOpenClothBase;TombBody;isoContainers;PROJECTRVInterior42;LargeProgressIndicator;CleanUI;MiniHealthPlus;BravoItemDetails;DetailedDescriptionsForOccupationsAndTraits;MutiesContextMenuIcons;ItemCondition_KingEJ;SPNCC;SPNCCDetails;SPNCCDetailsHD;SeedSeasonIndicator;NeatUI_Hairstyler;Neat_Building;Neat_Building_AddonXP;Neat_Crafting;Neat_Crafting_AddonXP;DynamicMoodles;devcattpixelmoodles;redrawnmoodles;PMResurrection;PlainMoodles_by_Slobodskoy;quarterbordermoodles;SimpleMoodleIndicators;v1centesmoodles;v1centesmoodlesbeta;SandboxSettings;BeyondTen;HasResearch;WorkingKnowledge;SkillRecoveryJournal;P4HasBeenRead;P4PickingMeister;P4TidyUpMeister;CausaMortis;KI5campers;KI5trailers;TradingTrailer;49powerWagon;59meteor;63beetle;63Type2Van;65banshee;66pontiacLeMans;67commando;67gt500;68firebird;69camaro;69charger;69fordMustang;69fordMustangExtra;69mini;69mini_ItalianJob;69mini_PitbullSpecial;70barracuda;70chevelle;70dodge;70fordEscort;70roadRunner;73fordFalcon;73fordFalconPS;73nissanGTR;75grandPrix;76chevyKseries;76chryslerNewYorker;77firebird;78amgeneralM35A2;78amgeneralM35A2extra;78amgeneralM49A2C;78amgeneralM50A3;78amgeneralM62;78lamboCountach;79camaro;80manKat1;81deloreanDMC12;82firebird;82jeepJ10;82jeepJ10t;82oshkoshM911;82porsche911;84buickElectra;84cadillacDeVille;84corvette;84jeepXJ;84merc;84oldsmobile98;85buickLeSabre;85chevyCaprice;85chevyStepVan;85chevyStepVanexpanded;85oldsmobileDelta88;85pontiacParisienne;86chevyCUCV;86fordE150;86fordE150expanded;86fordE150pd;86oshkoshP19A;87buickRegal;87chevySuburban;87fordB700;87toyotaMR2;88chevyS10;88toyotaHilux;89defender;89dodgeCaravan;89fordBronco;89trooper;89volvo200;90bmwE30;90fordF350ambulance;90pierceArrow;91fordLTD;91fordRanger;91geoMetro;91lexusLS400;91nissan240sx;91range;92amgeneralM998;92amgeneralM998extra;92fordCVPI;92jeepYJ;92jeepYJJP18;92nissanGTR;93chevySuburban;93chevySuburbanExpanded;93fordElgin;93fordF350;93fordTaurus;93mustangSSP;93townCar;99fordCVPI;KYRRealWeatherMod;HereGoesTheSun;RainCleansBlood;CheckthePapers;DeadMansDossier;KnoxDetectionKit;ZVirusVaccine42BETA;LifestyleHobbies;GuitarMusicAddon;DeltaSeries;AP;AutoReload;AutoTailoring;better-auto-mechanics;Drink_Smart;dustinguished_bolt_cutters;FasterHoodOpening;KeepRadioOnVanillaFriendly;MoreUsesForLightersAndMatches;NoLighterNeeded;OCsPacking;SmarterStorage;STA_PryOpen;stanks_suicide;SwapIt;throw-your-bag-across;UseItemsWhileWalkingB42;OpenAllContainers;RebalancedPropMoving;RepairAnyClothes;ReducedWeightWalkieTalkies;WashFix;AutoExercisePlus;ExerciseWithGearAgain;w_betterwringing;DRAW_ON_MAP;FirstAidVHSTapes;NepCanteenSlot;TacHold AR;TacHold Pistol AR;ShelterHold_Beehive;Ladders4220;JWJ;ClothingLine;ComputerModkum;Buttstroke;CVI;CVLS;FunctionalAppliances2;JB_BugsInLights;LWBetterElectronics;MoreDamagedObjects;PIP;playable_pool;PSR;RepairWallCracks_B42;SomewhatWater;Snowman;SkillBookExpansionB42;UseulTents;WorkingVending;Video_Game_Consoles;VGC_Addon_NESGames;VGC_Addon_SEGAGames;VGC_Addon_SNESGames;VGC_Addon_SNESGames2;NewMusic;NewMusicG70s;NewMusicG80s;NewMusicG90s;NMGreatestHits;NewMusic_CMM;Project_Cook;B42Horticulture;twistonfirebetterfishing;SKITTLE_LongTermPreservation4220;VanillaFoodsExpanded;SapphCooking_B42;Makedriedfruit;Makedriedvegetable;Makefruitinjar;Mushroomjar;ServingPlates42;Edwin''sCannedTent;Edwin''sCannedTobaccoKit;dustinguished_cleaning_wipes;ExtraBombs2;ammomaker;B42Survival;SWMG;MarzGuns;LEGION18;SpnOpenCloth;SpnCloth;FH;SpnHair;VanillaGearExpanded;ArmoredVests;ArmoredVestsPatch;AliceGear;[J&G] Firefighter Uniform;[J&G] SWAT Uniform;[J&G] Trauma Responder Uniform;[J&G] Desert Multicam Uniform;DarkWpnSlings;GanydeBielovzki''s Frockin Splendor!;GanydeBielovzki''s Frockin Splendor! Vol.2;GanydeBielovzki''s Frockin Splendor! Vol.3;GanydeBielovzki''s Frockin Splendor! Vol.4;GanydeBielovzki''s Frockin Splendor! Vol.5;GanydeBielovzki''s Frockin Shirts n Ties;GanydeBielovzki''s Frockin Wiseguys;GanydeBielovzki''s Frockin Stompers! VFR;CorvusNVG;AccCamos;AccCamosPeriodPack;TombBodyCompat;TombBodyTex;TombBodyCustom;ProjectArcade;BCR;DG_MIVehicles;WayMoreCars;VMZNEW;VVR;RealisticDash;GasPumpIndicator;immersive_hotwire;NepHighBeams;TaillightsAndStoplights;VehicleDoorsHotkey;VehicleRepairOverhaul;VehicleSalvageOverhaulB42;WeightReduction;ECTO1;69mini_MrBean;81deloreanDMC12BTTF;82firebirdKITT;86fordE150dnd;86fordE150mm;SimpleOverhaulTraitsAndOccupations;GanydeBielovzki''s Frockin Stompers!;Buttstroke 42.12.3;CraftingXPDisplay;ClothingLineFix;KillCount;mapmodmanager;Neat_Building_Buildables_SESCompat;Neat_Building_Railings;Neat_Building_UIOnly;Project_Cook_Pixel_Icon_Pack;ReactiveSE;SomewhatWaterBright;SpawnSelector;SPNCCFaces;TacHold;TacHold Pistol;TombBodyTexDOLL;TombBodyTexNUDE;ResearchLabInternProfession;InvisibleWpnSlings;ZombsHaveCigs;RealisticKentuckyFarmingCalendar;SleepWithFriends;NepMoreOnFloor;odz_gasmaskM40_replacer;ChainsawB42;ItemRarityUI;GeneralMod;BarbedWireWrapBaseballBat;TwisTonFireFasterActions;SafeUserLogin;BBQProtection;AnotherVehicleClaimSystem;AnotherVehicleClaimSystemAddonContainersAccess;AnotherVehicleClaimSystemCraft;RUNE-EXP;StopJumpingOffStairs;PalmPilots;SawAllLogsDropPlanks;UBPropFix;SpareEnginePartsCrafting;opcontainer;NicksTurnOffFridges;CarRoofFix;NeatLockpicking;AdvancedTrapping;LGExtendedPlumbing;SkullysZombieCollision' $ServerHost = 'home.dehaven.info' $ServerPort = '16261' $CollectionUrl = 'https://steamcommunity.com/sharedfiles/filedetails/?id=3786094044' $ServerPassword = 'xdxdx' $BulkThreshold = 25 # above this, send them to the collection instead # Items the server needs that the upstream collection does not include. $Additions = @() function Say([string]$m, [string]$c = 'Gray') { Write-Host $m -ForegroundColor $c } function Head([string]$m) { Write-Host ''; Write-Host "== $m" -ForegroundColor Cyan } Head 'Locating your install' if ($WorkshopDir) { if (-not (Test-Path $WorkshopDir)) { Say "No such folder: $WorkshopDir" Red; exit 1 } Say " Workshop: $WorkshopDir (override)" $workshopDir = $WorkshopDir $skipSteamLookup = $true } # Steam records its own location in the registry; the library list then lives in # libraryfolders.vdf. Workshop content can sit on any library drive, not just # the one Steam itself is installed on, so both lookups are needed. $steamRoot = $null if (-not $skipSteamLookup) { foreach ($k in @('HKCU:\Software\Valve\Steam', 'HKLM:\SOFTWARE\WOW6432Node\Valve\Steam')) { try { $p = (Get-ItemProperty -Path $k -ErrorAction Stop).SteamPath if ($p -and (Test-Path $p)) { $steamRoot = $p; break } } catch { } } if (-not $steamRoot) { Say 'Could not find Steam in the registry.' Red Say 'If Steam is installed somewhere unusual, re-run with:' Red Say ' -WorkshopDir "D:\SteamLibrary\steamapps\workshop\content\108600"' Red exit 1 } Say " Steam: $steamRoot" $libraries = @($steamRoot) $vdf = Join-Path $steamRoot 'steamapps\libraryfolders.vdf' if (Test-Path $vdf) { foreach ($m in [regex]::Matches((Get-Content $vdf -Raw), '"path"\s+"([^"]+)"')) { $libraries += $m.Groups[1].Value -replace '\\\\', '\' } } foreach ($lib in ($libraries | Select-Object -Unique)) { $c = Join-Path $lib "steamapps\workshop\content\$AppId" if (Test-Path $c) { $workshopDir = $c; break } } } $zomboidDir = Join-Path $env:USERPROFILE 'Zomboid' Say " Zomboid: $zomboidDir$(if (-not (Test-Path $zomboidDir)) { ' (not created yet - run the game once)' })" if (-not $workshopDir) { Say ' Workshop: none found - you have no PZ mods downloaded yet.' Yellow $installed = @() } else { if (-not $skipSteamLookup) { Say " Workshop: $workshopDir" } # A subscribed-and-downloaded item is a numeric folder here. Steam creates # it on download, so this reflects what is actually on disk rather than what # Steam merely intends to fetch. $installed = @(Get-ChildItem $workshopDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d+$' } | ForEach-Object { $_.Name }) } Head 'Comparing against the server' $missing = @($Required | Where-Object { $installed -notcontains $_ }) $extra = @($installed | Where-Object { $Required -notcontains $_ }) Say (" server needs : {0}" -f $Required.Count) Say (" you have : {0}" -f $installed.Count) if ($missing.Count -eq 0) { Say (" MISSING : 0 - you have everything the server loads") Green } else { Say (" MISSING : {0}" -f $missing.Count) Red } if ($extra.Count -gt 0) { Say (" extra : {0} (not used by the server)" -f $extra.Count) Yellow } if ($missing.Count -gt 0) { # Two very different situations, and printing 282 URLs helps in neither. # Lots missing = they have not subscribed to the collection yet, so send # them there. A handful missing = the known partial-subscribe failure, and # that is where a per-item list actually earns its place. if ($missing.Count -gt $BulkThreshold) { Head 'You have not subscribed to the collection yet' Say "Missing $($missing.Count) of $($Required.Count) - start with the collection:" Yellow Say '' Say " $CollectionUrl" Cyan Say ' -> click "Subscribe to all"' $n = 2 foreach ($a in $Additions) { Say " $n. https://steamcommunity.com/sharedfiles/filedetails/?id=$($a.Id)" Cyan Say " -> $($a.Name) - NOT in the collection, subscribe separately" $n++ } Say '' Say 'Then let Steam finish downloading and run this again. It will list' Say 'anything the "Subscribe to all" button missed, which is common on a' Say 'collection this size.' } else { Head "Missing items ($($missing.Count))" Say 'Steam''s "Subscribe to all" is known to stall part-way through large' Say 'collections, which is usually why a few are left over.' Say '' foreach ($id in $missing) { $note = ($Additions | Where-Object { $_.Id -eq $id } | Select-Object -First 1) $tag = if ($note) { " <- $($note.Name), not in the collection" } else { '' } Say " https://steamcommunity.com/sharedfiles/filedetails/?id=$id$tag" } Say '' if ($OpenMissing) { $take = [Math]::Min($missing.Count, $OpenLimit) Say "Opening $take in Steam..." Cyan foreach ($id in ($missing | Select-Object -First $take)) { Start-Process "steam://url/CommunityFilePage/$id" Start-Sleep -Milliseconds 400 # let Steam keep up } } else { Say 'Re-run with -OpenMissing to have Steam open these pages for you.' Cyan } } } if ($extra.Count -gt 0) { Head 'Extra items (usually harmless)' Say 'These are subscribed but not used by the server. They only cause trouble' Say 'if you leave them ENABLED - leftover Build 41 mods are the most common' Say 'cause of a failed join. If you get kicked, unsubscribe these first.' Say '' foreach ($id in ($extra | Select-Object -First 20)) { Say " https://steamcommunity.com/sharedfiles/filedetails/?id=$id" } if ($extra.Count -gt 20) { Say (" ... and {0} more" -f ($extra.Count - 20)) } } Head 'Mod preset (optional - NOT needed to join)' # Verified against the game's own code rather than guessed: on connect, # ConnectToServerState receives the server's mod list, calls loadModsAux, and # fails only with UI_OnConnectFailed_ModRequired = "Mod %1 is not installed". # There is no "not enabled" failure path anywhere in the game. So the server # activates its own mod set on the client; the player only has to have the mods # downloaded. This preset is therefore purely a convenience for playing the # same modset in SINGLE player. $presetFile = Join-Path ([Environment]::GetFolderPath('Desktop')) 'EldradiaPZ-preset.txt' Set-Content -Path $presetFile -Value $Preset -Encoding UTF8 Say " Written to: $presetFile" try { Set-Clipboard -Value $Preset Say ' Copied to your clipboard.' Green } catch { Say ' (Could not reach the clipboard - copy it from the file above.)' Yellow } Say '' Say ' You do NOT need this to join the server - it enables the mods for you.' Say ' Use it only if you want the same modset in single player:' Say ' Mods menu -> import/paste preset.' Head 'Connect' Say " $ServerHost : $ServerPort" Say " Password: $ServerPassword" Write-Host '' if ($missing.Count -eq 0) { Say 'READY - you have everything installed. Just connect.' Green Say 'The server enables its own mods for you; nothing to turn on first.' Green } else { Say ("NOT READY - {0} item(s) missing. The game will refuse the connection" -f $missing.Count) Red Say 'with "Mod ... is not installed". Subscribe to them above, let Steam' Red Say 'finish downloading, then run this again.' Red } Write-Host ''