Projects, MSBuild and the dotnet CLI

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402, vbc compiler version 5.9.0-1.26505.111) and was checked on 2026-10-19. Everything marked "built and run" was produced in the .NET 10 SDK container (Linux, arm64) with the real dotnet and vbc commands; the output shown is the output printed. Compiler and MSBuild messages are quoted as printed, so root namespaces and paths in them (/w/...) come from the scratch projects. Project examples are small console programs that start with Option Strict On, or are project files with no code at all. Anything that needs Windows is labelled "built, not run" or "evaluated only". Properties and options are listed by their real names, and the lists were checked against the files the SDK ships (the VB .props and .targets files, Microsoft.VisualBasic.Core.targets and the output of vbc -help).

The project file is where almost every compiler setting in a Visual Basic program lives, and the VB defaults differ from the C# defaults in ways that matter: the SDK ships OptionStrict as Off and a NoWarn list that hides the Option Strict warnings, and several properties known from C# do nothing. Reading this page you will learn what a modern .vbproj contains, how each property becomes a vbc option (all of them, by name), which properties are shared with C# and which exist only for VB, how to set the language version, imports, conditional-compilation symbols and warning policy, how to share settings across a solution, how the dotnet command line drives the same machinery, how Visual Studio’s property pages map onto it, and how the legacy (non-SDK) project format differs. The language-level meaning of Option Strict and its siblings is on Option Statements, the #If directive and symbols on Conditional Compilation and Directives, the My types on The My Namespace, and packaging and publish modes on Libraries, Packaging and Deployment.

The SDK-style project file

A Visual Basic project today is an SDK-style project: a short XML file whose root element names an SDK (Sdk="Microsoft.NET.Sdk"). The SDK supplies, through imported .props and .targets files, everything a project used to list by hand: the source files (a glob), the references to the framework, the default imports, the compiler settings and the build targets. This is what dotnet new writes for the three most common project types (the sources are in the next sections; the listing shows the project files exactly as the templates wrote them):

<!-- dotnet new classlib -lang VB -o Shelf.Core -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <RootNamespace>Shelf.Core</RootNamespace>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>

</Project>

<!-- dotnet new console -lang VB -o Shelf.Cli, then: dotnet add Shelf.Cli reference Shelf.Core -->
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\Shelf.Core\Shelf.Core.vbproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <RootNamespace>Shelf.Cli</RootNamespace>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>

</Project>

<!-- dotnet new mstest -lang VB -o Shelf.Tests -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MSTest" Version="4.0.2" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
  </ItemGroup>

</Project>

Three things stand out. A library has no OutputType (the default is Library), a console application has OutputType Exe, and RootNamespace is written into Visual Basic templates (C# templates have no such property, see the next table). The test template carries LangVersion latest (harmless: it means the newest version) and three C#-isms, ImplicitUsings, Nullable and a Using item, that have no effect in a Visual Basic project: the captured vbc command lines in this page do not change when they are set (see Properties that do nothing in Visual Basic). The test template’s own sources and the way the tests run are on Testing.

The project name matters more in VB than in C#, because three names are derived from it and they are independent:

Property Controls Default

AssemblyName

The file name of the output (Shelf.Core.dll) and the assembly identity.

The project file name without extension.

RootNamespace

The namespace the compiler prefixes to every Namespace block and to types declared outside one (/rootnamespace). It also appears in CLR exception messages and in the name of embedded resources and My types.

The project file name, with characters that are not valid in an identifier replaced.

PackageId

The NuGet package id (see Libraries, Packaging and Deployment).

AssemblyName.

What the SDK adds to your project

The following are not in the project file above, but are in force for every Microsoft.NET.Sdk Visual Basic project. Each was observed with dotnet msbuild -getProperty / -getItem, or in the captured command line below.

Implicit What it is

Source files

Compile Include="**/*.vb", minus bin, obj and other default excludes; switched off by EnableDefaultCompileItems = false. **/*.resx become EmbeddedResource items, everything else is None. Putting a file into the folder is enough; listing it again is an error (NETSDK1022, shown below).

Framework reference

The Microsoft.NETCore.App reference pack for the target framework: 167 reference assemblies (for net10.0 on this SDK) passed as /reference:, plus /nostdlib /nosdkpath so the compiler never looks anywhere else.

Visual Basic runtime

Microsoft.VisualBasic.dll (which type-forwards to Microsoft.VisualBasic.Core) passed as /vbruntime:<path>, so Left, Mid, CInt, Format, Like and the My machinery are available without a Reference.

Global imports

/imports:Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Diagnostics,System.Linq,System.Xml.Linq,System.Threading.Tasks (System.Data is added on .NET Framework targets).

Option defaults

OptionStrict Off, OptionExplicit On, OptionInfer On, OptionCompare Binary.

Warning defaults

NoWarn = 41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 (plus BC41997 added on .NET targets), WarningLevel 1, and WarningsAsErrors NU1605;SYSLIB0011.

Conditional symbols

CONFIG, DEBUG, TRACE, PLATFORM, _MyType, and the target framework symbols (NET, NET10_0, NETCOREAPP, …​); see DefineConstants and the implicit symbols.

Generated source

obj/<config>/<tfm>/<name>.AssemblyInfo.vb (assembly attributes from Version, Company and friends, when GenerateAssemblyInfo is true) and obj/.../.NETCoreApp,Version=v10.0.AssemblyAttributes.vb (the TargetFramework attribute).

Analyzers

The .NET analyzers (Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.dll and Microsoft.CodeAnalysis.NetAnalyzers.dll) and a global analyzer config for AnalysisLevel.

Where does it come from? The Sdk attribute makes MSBuild import Sdk.props before the project body and Sdk.targets after it. The VB-specific pieces are two small files in the SDK’s Sdks/Microsoft.NET.Sdk/targets folder, selected by the project extension:

File (under the SDK folder) Contents

Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.VisualBasic.props

Imported by Microsoft.NET.Sdk.props when the project extension is .vbproj. Defaults for WarningLevel, NoWarn, OptionExplicit, OptionCompare, OptionStrict, OptionInfer, WarningsAsErrors (adds NU1605) and DefineDebug / DefineTrace per configuration.

Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.VisualBasic.targets

Imported by Microsoft.NET.Sdk.targets when the language is VB. The implicit Import items, FinalDefineConstants, the VBRuntime / UseReferencedVBRuntime logic, DisableSdkPath, the _MyType="Empty" define, the SupportsHotReload capability and the SYSLIB0011 rule.

Roslyn/Microsoft.VisualBasic.Core.targets

The CoreCompile target, which calls the Vbc task with 90 parameters, each fed by one property or item (listed below).

Roslyn/Microsoft.Managed.Core.targets

Behaviour shared with C#: UseSharedCompilation (default true), analyzer skipping, the Features property, source link and embedded files.

Because those are plain text files in the installed SDK, every statement about defaults on this page can be checked on your machine with dotnet msbuild -getProperty:Name, and the files are the authoritative reference when the documentation lags behind (it does, as the sections below point out).

The default items and the NETSDK1022 error

The glob is the single most common cause of a first migration error: a project converted from the old format still lists every file, so the SDK sees each file twice.

<!-- P.vbproj : SDK-style project that also lists a file the glob already included -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>P</RootNamespace>
    <NuGetAudit>false</NuGetAudit>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.vb" />
  </ItemGroup>

</Project>
$ dotnet build
/usr/share/dotnet/sdk/10.0.402/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.DefaultItems.Shared.targets(190,5): error NETSDK1022: Duplicate 'Compile' items were included. The .NET SDK includes 'Compile' items from your project directory by default. You can

(The message continues with the two ways out; the line is cut here.) You either delete the Compile item, or switch the glob off with EnableDefaultCompileItems set to false and list every file yourself. To exclude files from the glob, use Remove:

  <ItemGroup>
    <Compile Remove="Old/**" />
  </ItemGroup>

With Old/Legacy.vb (a second Sub Main in the same folder tree) the project built with 0 warnings and 0 errors and printed main, and dotnet msbuild -getItem:Compile listed only Program.vb: the removed file was never compiled. With EnableDefaultCompileItems set to false and <Compile Include="Program.vb" /> the same query also listed only Program.vb. Built and run: 2 projects.

From project properties to vbc options

dotnet build does not understand Visual Basic; it asks MSBuild to evaluate the project and run its targets. One of those targets, CoreCompile, calls the Vbc task, and the task turns property values into a command line for the Roslyn Visual Basic compiler. So a property is never "applied" by the compiler: each property named below is read by the SDK’s targets and turned into zero or more vbc options. The diagram shows the path, and the table after it lists the option each property produces.

flowchart TD subgraph Evaluate["1. Evaluation: properties and items"] A["Directory.Build.props, then the .vbproj body, then -p:Name=Value from the command line"] B["Microsoft.NET.Sdk.VisualBasic.props sets OptionStrict Off, OptionExplicit On, OptionInfer On, OptionCompare Binary, NoWarn, DefineDebug, DefineTrace"] C["Microsoft.NET.Sdk.VisualBasic.targets builds Import items, FinalDefineConstants, VBRuntime, DisableSdkPath"] D["Items: Compile, Reference, Import, EmbeddedResource, Analyzer, AdditionalFiles, LinkResource"] A --> B --> C end subgraph Compile["2. CoreCompile target"] E["Vbc task in Microsoft.VisualBasic.Core.targets: 90 parameters, each fed by one property or item"] F["Command line written to a response file, one option per line"] G{"UseSharedCompilation"} H["VBCSCompiler build server, reused between builds"] I["vbc.dll in a new process"] E --> F --> G G -->|true, the default| H G -->|false| I end C --> E D --> E H --> J["obj/Debug/net10.0: App.dll, App.pdb, App.xml, refint/App.dll"] I --> J

The second diagram groups the properties by what they control and shows the vbc options they become. Where a property has no option of its own (Configuration, DefineDebug) it feeds an option indirectly; the full table is in the property reference and the option side is in the vbc option reference.

flowchart LR subgraph Props["MSBuild properties and items"] P1["OutputType, AssemblyName, RootNamespace, StartupObject, PlatformTarget"] P2["LangVersion, OptionStrict, OptionExplicit, OptionInfer, OptionCompare, RemoveIntegerChecks"] P3["DefineConstants, DefineDebug, DefineTrace, Configuration, TargetFramework"] P4["Import items, DisableImplicitNamespaceImports, MyType, VBRuntime"] P5["NoWarn, WarningLevel, TreatWarningsAsErrors, WarningsAsErrors, WarningsNotAsErrors"] P6["Optimize, DebugType, DebugSymbols, Deterministic, PathMap, ProduceReferenceAssembly"] P7["GenerateDocumentationFile, DocumentationFile, ErrorLog, CodeAnalysisRuleSet"] P8["Reference, ProjectReference, PackageReference, Analyzer, EmbeddedResource items"] end subgraph Opts["vbc options"] O1["-target, -out, -rootnamespace, -main, -platform"] O2["-langversion, -optionstrict, -optionexplicit, -optioninfer, -optioncompare, -removeintchecks"] O3["-define"] O4["-imports, -vbruntime, -nosdkpath"] O5["-nowarn, -warnaserror"] O6["-optimize, -debug, -deterministic, -pathmap, -refout"] O7["-doc, -errorlog, -ruleset"] O8["-reference, -analyzer, -resource"] end P1 --> O1 P2 --> O2 P3 --> O3 P4 --> O4 P5 --> O5 P6 --> O6 P7 --> O7 P8 --> O8

The real command line

The next project sets 18 properties and one Import item. It is a deliberately busy .vbproj; every property is explained in the sections below.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>Ks</RootNamespace>
    <AssemblyName>Ks</AssemblyName>
    <StartupObject>Ks.Program</StartupObject>
    <LangVersion>latest</LangVersion>
    <OptionStrict>On</OptionStrict>
    <OptionExplicit>On</OptionExplicit>
    <OptionInfer>On</OptionInfer>
    <OptionCompare>Text</OptionCompare>
    <RemoveIntegerChecks>true</RemoveIntegerChecks>
    <DefineConstants>$(DefineConstants),FEATURE_X=1,GREETING="hi"</DefineConstants>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <WarningsNotAsErrors>BC42024</WarningsNotAsErrors>
    <NoWarn>$(NoWarn);BC42350</NoWarn>
    <Deterministic>true</Deterministic>
    <DebugType>embedded</DebugType>
    <Optimize>false</Optimize>
    <PlatformTarget>x64</PlatformTarget>
    <NuGetAudit>false</NuGetAudit>
  </PropertyGroup>

  <ItemGroup>
    <Import Include="System.Text" />
  </ItemGroup>

</Project>

dotnet build -v:diag prints the command line the Vbc task ran. Shown here one option per line with the 167 reference assemblies and a few paths shortened and the NETnn_0_OR_GREATER version symbols dropped from /define:

<sdk>/Roslyn/bincore/vbc
/noconfig
/imports:System.Text,Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Diagnostics,System.Linq,System.Xml.Linq,System.Threading.Tasks
/optioncompare:Text
/optionexplicit+
/optionstrict+
/nowarn:41999,42016,42017,42018,42019,42020,42021,42022,42032,42036,BC42350,BC41997
/nosdkpath
/optioninfer+
/nostdlib
/errorreport:prompt
/platform:x64
/removeintchecks+
/rootnamespace:Ks
/highentropyva+
/vbruntime:<ref>/Microsoft.VisualBasic.dll
/doc:obj/Debug/net10.0/Ks.xml
/define:"CONFIG=\"Debug\",DEBUG=-1,TRACE=-1,PLATFORM=\"AnyCPU\",,FEATURE_X=1,GREETING=\"hi\",_MyType=\"Empty\",NET=-1,NET10_0=-1,NETCOREAPP=-1"
/reference:<ref>/Microsoft.CSharp.dll,<ref>/Microsoft.VisualBasic.Core.dll,... (167 assemblies)
/main:Ks.Program
/debug+
/debug:embedded
/filealign:512
/optimize-
/out:obj/Debug/net10.0/Ks.dll
/refout:obj/Debug/net10.0/refint/Ks.dll
/target:exe
/warnaserror+
/utf8output
/deterministic+
/langversion:latest
/analyzerconfig:obj/Debug/net10.0/Ks.GeneratedMSBuildEditorConfig.editorconfig
/analyzerconfig:<sdk>/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig
/analyzer:<sdk>/Sdks/Microsoft.NET.Sdk/targets/../analyzers/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.dll
/analyzer:<sdk>/Sdks/Microsoft.NET.Sdk/targets/../analyzers/Microsoft.CodeAnalysis.NetAnalyzers.dll
Program.vb
"obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.vb"
obj/Debug/net10.0/Ks.AssemblyInfo.vb
/warnaserror+:NU1605,SYSLIB0011
/warnaserror-:BC42024

(<sdk> is /usr/share/dotnet/sdk/10.0.402 and <ref> is the .NET 10 reference pack folder.) Read it against the project:

  • OptionCompare Text became /optioncompare:Text; OptionStrict On became /optionstrict+; RemoveIntegerChecks became /removeintchecks+.

  • NoWarn got the SDK’s default list plus BC42350 plus BC41997, which the SDK appends for .NET (Core) targets. The leading BC is accepted.

  • DefineConstants is a comma separated list; the SDK prepends CONFIG, DEBUG, TRACE and PLATFORM, leaves an empty entry (,,) where the empty $(DefineConstants) was, and appends _MyType="Empty" and the framework symbols. The string value GREETING="hi" had its quotes escaped.

  • StartupObject became /main:Ks.Program, and GenerateDocumentationFile became /doc: with a path under obj.

  • TreatWarningsAsErrors became /warnaserror+, WarningsNotAsErrors a trailing /warnaserror-:BC42024, and the SDK’s own WarningsAsErrors (NU1605;SYSLIB0011) a /warnaserror+: list.

  • /noconfig, /nostdlib, /nosdkpath, /errorreport:prompt, /highentropyva+, /filealign:512, /utf8output, /refout: and the two analyzers were not asked for: they are SDK defaults.

  • The generated AssemblyInfo.vb and the assembly-attributes file are compiled together with your source.

The command line is the same one you would type by hand (see Running vbc by hand); only -v:diag or a binary log shows it during a normal build.

Printing the command line without compiling

Reading -v:diag output (it is roughly 50,000 lines for a tiny project) is clumsy. The SDK has a switch meant for IDEs, ProvideCommandLineArgs, which makes the Vbc task return its arguments as an item instead of running the compiler. With the -getTargetResult switch of dotnet msbuild you get one JSON entry per argument:

dotnet msbuild Map.vbproj -nologo -v:q -t:ResolveReferences,CoreCompile \
  -p:SkipCompilerExecution=true -p:ProvideCommandLineArgs=true -getTargetResult:CoreCompile

This is how every row of the property table below was produced: the command is run with the baseline project and once more with one extra -p:Name=Value, and the lines that changed are the option(s) that property controls. The baseline (a dotnet new console -lang VB project renamed Map, default configuration) is:

/noconfig
/imports:Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Diagnostics,System.Linq,System.Xml.Linq,System.Threading.Tasks
/optioncompare:Binary
/optionexplicit+
/optionstrict:custom
/nowarn:41999,42016,42017,42018,42019,42020,42021,42022,42032,42036,BC41997
/nosdkpath
/optioninfer+
/nostdlib
/errorreport:prompt
/rootnamespace:Map
/highentropyva+
/vbruntime:<ref>/Microsoft.VisualBasic.dll
/define:"CONFIG=\"Debug\",DEBUG=-1,TRACE=-1,PLATFORM=\"AnyCPU\",_MyType=\"Empty\",NET=-1,NET10_0=-1,NETCOREAPP=-1,..."
/debug+
/debug:portable
/filealign:512
/optimize-
/out:obj/Debug/net10.0/Map.dll
/refout:obj/Debug/net10.0/refint/Map.dll
/target:exe
/warnaserror-
/utf8output
/deterministic+
/warnaserror+:NU1605,SYSLIB0011

The first surprise is /optionstrict:custom. The SDK’s default of OptionStrict Off does not become /optionstrict-; it becomes custom, a mode that raises the individual Option Strict diagnostics as warnings. Those warnings (BC42016 to BC42022 and a few more) are then silenced by the SDK’s default NoWarn, which is why a default project accepts implicit narrowing silently. The consequence is demonstrated in The NoWarn trap.

Default values of a VB console project

dotnet msbuild -getProperty:Name prints the final value of a property after evaluation, with no build. Run on a fresh dotnet new console -lang VB project renamed App (net10.0, Debug unless stated), it gives the following. An empty value means "not set"; the SDK’s targets then behave as the third column says.

Property Value Meaning of the default

OutputType

Exe

Set by the console template; a library project has no value and builds as Library.

AssemblyName, RootNamespace

App, App

Both come from the project file name.

StartupObject

(empty)

The compiler looks for exactly one Sub Main; two give BC30738.

LangVersion

(empty)

No /langversion option is passed, so the compiler default applies (default, which is 17.13 on this SDK).

OptionStrict

Off

Becomes /optionstrict:custom; see below.

OptionExplicit, OptionInfer, OptionCompare

On, On, Binary

Same as the language defaults.

RemoveIntegerChecks, MyType, VBRuntime

(empty)

Integer overflow is checked; _MyType="Empty" is defined; the runtime comes from UseReferencedVBRuntime.

UseReferencedVBRuntime

true

The SDK passes the reference pack’s Microsoft.VisualBasic.dll as /vbruntime:.

DefineDebug, DefineTrace

true, true (Debug); (empty), true (Release)

Decide whether DEBUG=-1 and TRACE=-1 are defined.

DefineConstants

(empty)

Your own symbols; the SDK builds the final list in FinalDefineConstants.

GenerateDocumentationFile

false

No /doc.

NoWarn

41999,42016,42017,42018,42019,42020,42021,42022,42032,42036

The SDK default list, hidden warnings of Option Strict Custom and similar.

WarningLevel, TreatWarningsAsErrors

1, false

Warnings are reported and do not fail the build.

WarningsAsErrors

;NU1605;SYSLIB0011

Two diagnostics are errors in every VB project.

Optimize, DebugSymbols, DebugType

false, true, portable (Debug); true, false, portable (Release)

Release optimizes and emits no PDB.

Deterministic, ProduceReferenceAssembly

true, true

/deterministic+ and /refout:obj/.../refint/App.dll.

PlatformTarget, Prefer32Bit

(empty), false

AnyCPU semantics; no /platform option.

FileAlignment, HighEntropyVA, Utf8Output, ErrorReport

512, true, true, prompt

/filealign:512, /highentropyva+, /utf8output, /errorreport:prompt.

GenerateAssemblyInfo

true

The SDK writes obj/.../App.AssemblyInfo.vb.

AnalysisLevel, EnableNETAnalyzers, EnforceCodeStyleInBuild

latest, true, false

The .NET analyzers run; code-style rules are IDE-only unless you opt in (see XML Documentation, Analyzers and Code Style).

UseSharedCompilation, RunAnalyzers

(empty), (empty)

The build server is used (the targets default it to true); analyzers run.

EnableDefaultCompileItems

true

The **/*.vb glob.

Nullable, ImplicitUsings, InvariantGlobalization

(empty)

Not set, and Nullable and ImplicitUsings are ignored by VB anyway.

TargetFrameworkIdentifier, TargetFrameworkVersion

.NETCoreApp, v10.0

Derived from TargetFramework; used by many conditions in the SDK files.

The Configuration changes the generated options as follows (captured with the same method, one extra -p:Configuration=...):

Configuration Symbols in /define Debug options Other options

Debug (default)

CONFIG="Debug", DEBUG=-1, TRACE=-1

/debug+ and /debug:portable

/optimize-, output under obj/Debug/net10.0

Release

CONFIG="Release", TRACE=-1, RELEASE=-1

/debug-

/optimize+, output under obj/Release/net10.0

Staging (a custom name)

CONFIG="Staging", STAGING=-1 (no TRACE, no DEBUG)

/debug-

/optimize-, output under obj/Staging/net10.0

The symbol that equals the upper-cased configuration name (RELEASE, STAGING) comes from ImplicitConfigurationDefine; set DisableImplicitConfigurationDefines to true to stop that. DEBUG and TRACE come from DefineDebug and DefineTrace, which the VB .props file sets per configuration. Setting DefineDebug to false on a Debug configuration does not remove DEBUG: the observed list still contains DEBUG=-1 (it moves to the end), because the implicit configuration define re-adds it. To really drop DEBUG in a Debug build, change Configuration, or set DisableImplicitConfigurationDefines and DefineDebug to false together.

Property reference

Every MSBuild property the Visual Basic targets read is listed here by group, each with its default, the vbc option it produces (verified with the method above) and its purpose. The Vbc task parameter list in The Vbc task parameters is the complete set; properties that exist only to be read by other targets (Version, Company, PackageId) are covered in Libraries, Packaging and Deployment. The values in the "Observed" column were emitted by the property named, in an otherwise default project.

Output and identity

Property Observed vbc option Default Purpose

OutputType

/target:exe / winexe / library / module / appcontainerexe / winmdobj

Library (templates write Exe or WinExe)

The kind of output. Exe is a console application, WinExe a GUI application (no console window), Library a .dll, Module a .netmodule for /addmodule, AppContainerExe and WinMDObj are for the Windows Runtime.

TargetFramework

none; selects the reference pack, the /vbruntime, the version symbols (NET10_0) and, for net481, /subsystemversion:6.00

set by the template

The target (net10.0, net8.0, net481, netstandard2.0, net10.0-windows). See Multi-targeting.

TargetFrameworks

none

empty

A semicolon separated list; MSBuild builds the project once per entry.

AssemblyName

/out:obj/Debug/net10.0/Name.dll

project name

Name of the output file and the assembly identity.

RootNamespace

/rootnamespace:Acme.Shelf

project name

Prefix applied to every namespace. See the previous section on names.

StartupObject

/main:Ks.Program

empty

The type that contains Sub Main (or a Form), written with the root namespace. Needed only when there are several entry points. Not passed to vbc when its value is Sub Main, the value the Windows Forms template writes.

PlatformTarget

/platform:x86 / x64 / arm64 / AnyCPU

empty (AnyCPU behaviour)

CPU the assembly may run on. Prefer32Bit true turns AnyCPU into /platform:anycpu32bitpreferred.

Prefer32Bit

/platform:anycpu32bitpreferred

false

Meaningful for .NET Framework executables.

FileAlignment

/filealign:1024

512

Alignment of PE sections.

HighEntropyVA

/highentropyva-

true

High-entropy address space layout randomization.

BaseAddress

/baseaddress:10000000 (given 0x10000000)

empty

Preferred load address of a library or module.

SubsystemVersion

/subsystemversion:6.02

empty (6.00 is added automatically for net481)

Minimum Windows subsystem version written into the PE header.

Version, AssemblyVersion, FileVersion, InformationalVersion, Company, Product, Authors, Copyright, Description

none

Version is 1.0.0

Read by the SDK to generate obj/.../Name.AssemblyInfo.vb (when GenerateAssemblyInfo is true). A Version of 1.2.3-beta.1 appears as the informational version of the assembly (shown in the demonstration below). GenerateAssemblyInfo false is needed when a hand-written AssemblyInfo.vb already declares the same attributes, otherwise BC30663 (duplicate attribute) is reported.

Language

Property Observed vbc option Default Purpose

LangVersion

/langversion:15.5, latest, default

empty (no option)

Restricts the compiler to the syntax of an older language version. Values and the tested feature matrix are in LangVersion.

OptionStrict

/optionstrict+ for On; /optionstrict:custom for Off

Off

On or Off only: the MSBuild property is a Boolean, Custom stops the build with MSB4030 (shown below). A file-level Option Strict statement overrides it.

OptionExplicit

/optionexplicit- for Off

On

Require declaration of variables. A file-level Option Explicit overrides it.

OptionInfer

/optioninfer- for Off

On

Allow local type inference.

OptionCompare

/optioncompare:Text

Binary

Binary or Text: how strings, Like, InStr, StrComp and Select Case compare. A file-level Option Compare overrides it. Text also changes which library functions can be translated by EF Core and what = means; see Option Statements.

RemoveIntegerChecks

/removeintchecks+

empty (checked)

Removes the overflow checks on Integer, Long, Short, Byte arithmetic and conversions, so results wrap silently (demonstrated below). Checked / Unchecked blocks do not exist in VB; this is the only switch.

MyType

defines _MyType="Console", "Windows", …​ (no option of its own)

empty (_MyType="Empty" on .NET)

Chooses which My classes the compiler generates. Templates write it; see The My Namespace.

VBRuntime

/vbruntime* for Embed; /vbruntime+ for Default; /vbruntime:path for a path

computed

Which Visual Basic runtime the program uses (see MyType and the Visual Basic runtime).

UseReferencedVBRuntime

/vbruntime:<reference pack>/Microsoft.VisualBasic.dll when true

true on .NET 3.0 and later, empty on netstandard and .NET Framework

Take the runtime from the Microsoft.VisualBasic assembly among the references. false makes the SDK embed the runtime (/vbruntime*).

NoVBRuntimeReference

/novbruntimeref

empty

Do not reference the Visual Basic runtime at all (advanced; the program can then not use the Microsoft.VisualBasic functions).

DefineConstants

/define: entries (comma separated)

empty

Your conditional-compilation symbols. See DefineConstants and the implicit symbols.

DefineDebug, DefineTrace, DisableImplicitConfigurationDefines

DEBUG=-1, TRACE=-1, and the configuration symbol, inside /define:

per configuration

See the Configuration table above.

<Import Include="..."/> items

/imports:A,B,C

eight namespaces (nine on .NET Framework)

Namespaces imported into every file. See Global imports.

DisableImplicitNamespaceImports

removes the default /imports: namespaces

equals DisableImplicitFrameworkReferences

Stop the SDK adding its Import items. Your own Import items are kept.

Features

/features:strict

empty

Experimental compiler feature flags, passed unchanged as /features:. The compiler accepted strict; the supported names are Roslyn internals that change between releases.

Diagnostics and documentation

Property Observed vbc option Default Purpose

GenerateDocumentationFile

/doc:obj/Debug/net10.0/Name.xml

false

Writes the XML documentation file (see XML Documentation, Analyzers and Code Style).

DocumentationFile

/doc:obj/Debug/net10.0/docs/Map.xml (given docs/Map.xml)

empty

Choose the file name; setting it enables documentation generation by itself, and the path is relative to obj/<config>/<tfm>.

NoWarn

/nowarn:42024,BC41997 (the list replaces the SDK’s list when you assign it)

41999,42016,...,42036

Warnings that are not reported. The BC prefix is optional. Always write $(NoWarn);BC42024 (see the trap below).

WarningLevel

/nowarn (all warnings off) for 0

1

0 hides every warning, 1 reports them.

TreatWarningsAsErrors

/warnaserror+

false

Every warning becomes an error.

WarningsAsErrors

/warnaserror+:BC42024

NU1605;SYSLIB0011

Named warnings become errors. The SDK already contains two; append with $(WarningsAsErrors);BC42024.

WarningsNotAsErrors

/warnaserror-:BC42024

empty

Keeps named warnings as warnings even when TreatWarningsAsErrors is true.

CodeAnalysisRuleSet

/ruleset:file

empty

A legacy .ruleset file that raises or lowers diagnostics. Prefer .editorconfig.

ErrorLog

/errorlog:build.sarif

empty

Writes all compiler and analyzer diagnostics in SARIF format.

ReportAnalyzer

/reportanalyzer

false

Prints analyzer timing.

RunAnalyzers, RunAnalyzersDuringBuild

/skipanalyzers+ when false

empty (analyzers run)

Do not run analyzers in this build.

AnalysisLevel, AnalysisMode, EnableNETAnalyzers, EnforceCodeStyleInBuild

change the /analyzer: and /analyzerconfig: entries only

latest, empty, true, false

Select the .NET analyzer rule set and whether IDE code-style rules also run in the build.

ErrorReport

/errorreport:none

prompt

What the compiler does after an internal error.

Utf8Output

no option when false

true

Emit compiler messages in UTF-8.

Code generation, debugging and determinism

Property Observed vbc option Default Purpose

Optimize

/optimize+

false Debug, true Release

Enable compiler optimizations.

DebugSymbols

/debug+; /debug- when false

true Debug, false Release

Emit debugging information at all.

DebugType

/debug:portable / full / pdbonly / embedded; none gives /debug-

portable

Kind of PDB. embedded puts it inside the assembly. embedded places the PDB inside the assembly; full and pdbonly are the older values kept for compatibility (all four were accepted by the compiler on Linux).

Deterministic

/deterministic+, /deterministic-

true

The same sources always produce the same bytes.

PathMap

/pathmap:/src=/w

empty

Rewrites source paths embedded in PDBs and attributes, for reproducible builds.

SourceLink

/sourcelink:sl.json

empty

Embeds Source Link data in the PDB; the compiler reports BC37264 if no PDB is emitted.

EmbedAllSources, EmbeddedFiles item

/embed, /embed:file

false

Embed source files into the PDB (needs a PDB: BC37265 otherwise).

ChecksumAlgorithm

/checksumalgorithm:SHA1

empty (SHA256 is the compiler default)

Algorithm for the source file checksums stored in the PDB.

CodePage

/codepage:1252

empty

Encoding to use for source files that have no byte-order mark.

ProduceReferenceAssembly

/refout:obj/Debug/net10.0/refint/Name.dll

true

Also emit a reference assembly (the metadata-only assembly other projects compile against). false drops /refout.

ProduceOnlyReferenceAssembly

/refonly (and /refout is dropped)

empty

Emit only the reference assembly.

Instrument

/instrument:TestCoverage

empty

Instrumentation for coverage tools. The runtime helper types must be available (BC35000 otherwise).

CompilerGeneratedFilesOutputPath

/generatedfilesout:gen

empty

Folder where source generators write their output.

Signing

Property Observed vbc option Purpose

SignAssembly, AssemblyOriginatorKeyFile

/keyfile:key.snk (full build)

SignAssembly true makes the ResolveKeySource target (in Microsoft.Common.CurrentVersion.targets) copy the resolved AssemblyOriginatorKeyFile into KeyOriginatorFile, which the Vbc task passes as /keyfile. That target belongs to a full build; the CoreCompile-only capture used for these tables does not run it, so only the direct KeyOriginatorFile form was observed.

KeyOriginatorFile

/keyfile:key.snk

Set directly (as the test above did) it is passed straight through.

PublicSign

/publicsign+

Public signing, the cross-platform way: only the public key is needed. Without a key the compiler reports BC37254 (shown below).

DelaySign

/delaysign+

Reserve space for the signature. Without a public key: warning BC40060.

KeyContainerName

/keycontainer:MyContainer

Key container signing; on .NET (Core) the compiler reports BC36981 "Assembly signing not supported". Use a key file.

Resources and Win32 metadata

Property or item Observed vbc option Purpose

ApplicationIcon

/win32icon:app.ico

Icon of the executable.

Win32Manifest

/win32manifest:app.manifest

Native application manifest (UAC, DPI, supported OS).

NoWin32Manifest

/nowin32manifest

Do not embed the default manifest.

Win32Resource

/win32resource:res.res

A compiled Win32 resource file.

EmbeddedResource items

/resource:file,name,public

Managed resources (.resx is compiled to .resources first).

LinkResource items

/linkresource:file,name,public

The file stays outside the assembly.

Windows desktop projects

Property Effect (observed on net10.0-windows, evaluated on Linux with EnableWindowsTargeting)

UseWindowsForms true

Adds the framework reference Microsoft.WindowsDesktop.App.WindowsForms next to Microsoft.NETCore.App, passes the Windows Desktop reference pack’s Microsoft.VisualBasic.dll as /vbruntime: (the one that contains Microsoft.VisualBasic.Forms), and adds WINDOWS=-1, WINDOWS7_0=-1 and WINDOWS7_0_OR_GREATER=-1 to /define. It does not add Import items (the template writes System.Data, System.Drawing and System.Windows.Forms itself) and does not set MyType.

UseWPF true

The same with Microsoft.WindowsDesktop.App.WPF. The template writes nine System.Windows.* Import items.

EnableWindowsTargeting true

Lets a non-Windows machine restore and build a net10.0-windows project; the result cannot be run there.

TargetFramework net10.0-windows

Gives SupportedOSPlatformVersion 7.0 by default (observed) and the WINDOWS symbols.

StartupObject Sub Main

The value the VB WinForms template writes: use the Sub Main in the project instead of the Application Framework. It is not turned into /main.

The winforms and wpf templates and what runs on startup are covered by Windows Forms Applications and WPF Applications.

The compiler host and miscellaneous

Property Observed vbc option Purpose

UseSharedCompilation

none

true (the default of the targets) sends the compile to the long-lived VBCSCompiler server process; false starts vbc.dll for each compile. Some CI setups turn the server off.

VbcToolPath, VbcToolExe

none

Run another vbc (an older or newer compiler) instead of the one in the SDK.

VbcVerbosity

/quiet and /verbose for the values quiet and verbose

Compiler message verbosity.

VbcEnvironment

none

Environment variables to set for the compiler process.

CompilerResponseFile

@extra.rsp

An extra response file whose lines are options.

AdditionalLibPaths

/libpath:/opt/libs

Folders searched for references.

NoCompilerStandardLib

/nostdlib (set by the SDK; false removes it)

Do not reference the standard libraries automatically.

DisableSdkPath

/nosdkpath, present in every build captured here (net10.0, net8.0, net481, netstandard2.0)

Do not search the compiler’s own SDK folder for the standard library. The task can pass /sdkpath from FrameworkPathOverride when this is not set.

TargetCompactFramework

/netcf

.NET Compact Framework (obsolete).

PreferredUILang

/preferreduilang:de-DE

Language of compiler messages.

ReportIVTs

/reportivts

Reports all InternalsVisibleTo grants.

ModuleAssemblyName

/moduleassemblyname:Shell

Only valid for OutputType Module, otherwise BC2030.

RuntimeMetadataVersion

/runtimemetadataversion:v4.0.30319

Passed by the task, but this compiler ignores it and prints warning BC2007 (unrecognized option).

SkipCompilerExecution, ProvideCommandLineArgs

none

Used to print the command line without compiling (see above).

VsSessionGuid, SharedCompilationId, PdbFile, UseHostCompilerIfAvailable

none

Internals used by the IDE and the build server.

Items that feed vbc

Item Observed vbc option Purpose

Compile

the source file names

Source files (default glob **/*.vb).

Reference, ProjectReference, PackageReference, FrameworkReference

/reference: list

Everything resolved to an assembly path (167 for a plain net10.0 console project).

COMReference

/reference: of the generated interop assembly; /link: with EmbedInteropTypes

Windows only (see COM Interop and Office Automation).

Import

/imports:

Global imports, one namespace per item.

Analyzer

/analyzer:

Roslyn analyzers and source generators from packages or the SDK.

AdditionalFiles

/additionalfile:

Non-code files visible to analyzers and generators.

EditorConfigFiles

/analyzerconfig:

The .editorconfig files and generated global configs the compiler reads.

AddModules

/addmodule:

Netmodules to combine into the assembly.

EmbeddedResource, LinkResource

/resource:, /linkresource:

See above.

Properties that do nothing in Visual Basic

C# project files and templates use several properties that look portable and are not. For each, the table says what was tested.

Property or item Result in a VB project

Nullable

No effect. Nullable enable produced no change in the vbc command line. Visual Basic has no nullable reference types; String is always nullable.

ImplicitUsings, <Using Include="...">

No effect. With ImplicitUsings enable and <Using Include="System.Text" />, Dim sb As New StringBuilder() failed with error BC30002: Type 'StringBuilder' is not defined.; replacing the Using item with <Import Include="System.Text" /> compiled and ran. The VB mstest template still writes both, harmlessly.

LangVersion preview

Rejected by the build: vbc : error BC2014: the value 'preview' is invalid for option 'langversion'. VB has no preview versions.

AllowUnsafeBlocks, CheckForOverflowUnderflow

No effect: neither changed the vbc command line. VB has no unsafe code; the nearest equivalent of the second is RemoveIntegerChecks (inverted meaning).

InvariantGlobalization

Not a compiler setting: it is written to runtimeconfig.json and read by the .NET runtime, so it works the same in VB.

What the properties do at run time

The next project reads its own build settings back, so one program shows five properties at once: RootNamespace, Version, OptionCompare, RemoveIntegerChecks and the conditional-compilation symbols. The project file sets OptionStrict and TreatWarningsAsErrors, and DefineConstants with two symbols of its own. Its root namespace is Shelf.Build. The code has no Imports for System, System.Collections.Generic or System.Linq: they come from the SDK’s implicit Import items.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>Shelf.Build</RootNamespace>
    <OptionStrict>On</OptionStrict>
    <Version>1.2.3-beta.1</Version>
    <DefineConstants>$(DefineConstants),FEATURE_LOANS=True,EDITION="Community"</DefineConstants>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <NuGetAudit>false</NuGetAudit>
  </PropertyGroup>

</Project>
Option Strict On

Imports System.Reflection

Namespace Reports

    Public Module Program

        Public Sub Main()
            ' No Imports statement names System, System.Collections.Generic or System.Linq:
            ' they come from the SDK's implicit Import items.
            Dim titles As New List(Of String) From {"Dune", "Emma"}
            Console.WriteLine("Titles: " & String.Join(", ", titles.OrderBy(Function(t) t)))

            Console.WriteLine("Type name: " & GetType(Program).FullName)
            Console.WriteLine("Assembly:  " & Assembly.GetExecutingAssembly().GetName().Name)

            Dim info As String = Assembly.GetExecutingAssembly() _
                .GetCustomAttribute(Of AssemblyInformationalVersionAttribute)().InformationalVersion
            Console.WriteLine("Version:   " & info.Split("+"c)(0))

            Console.WriteLine("Option Compare: ""a"" = ""A"" is " & ("a" = "A").ToString())

            Dim big As Integer = Integer.MaxValue
            Try
                big += 1
                Console.WriteLine("Integer overflow check: removed (wrapped to " & big.ToString() & ")")
            Catch ex As OverflowException
                Console.WriteLine("Integer overflow check: active (" & ex.GetType().Name & ")")
            End Try

            Dim symbols As New List(Of String)
#If DEBUG Then
            symbols.Add("DEBUG")
#End If
#If TRACE Then
            symbols.Add("TRACE")
#End If
#If NET10_0_OR_GREATER Then
            symbols.Add("NET10_0_OR_GREATER")
#End If
#If FEATURE_LOANS Then
            symbols.Add("FEATURE_LOANS")
#End If
#If CONFIG = "Release" Then
            symbols.Add("CONFIG=Release")
#ElseIf CONFIG = "Debug" Then
            symbols.Add("CONFIG=Debug")
#End If
#If EDITION = "Community" Then
            symbols.Add("EDITION=Community")
#End If
            Console.WriteLine("Symbols:   " & String.Join(" ", symbols))
        End Sub

    End Module

End Namespace

Five builds of the same source, each with --no-incremental into its own output folder (dotnet build -o out/<label>), then dotnet out/<label>/Props.dll. Each run changes one thing on the command line. A command-line -p: property wins over the project file:

== debug: dotnet build
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly:  Props
Version:   1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: active (OverflowException)
Symbols:   DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community

== release: dotnet build -c Release
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly:  Props
Version:   1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: active (OverflowException)
Symbols:   TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Release EDITION=Community

== nocheck: dotnet build -p:RemoveIntegerChecks=true
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly:  Props
Version:   1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: removed (wrapped to -2147483648)
Symbols:   DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community

== text: dotnet build -p:OptionCompare=Text
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly:  Props
Version:   1.2.3-beta.1
Option Compare: "a" = "A" is True
Integer overflow check: active (OverflowException)
Symbols:   DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community

== ns: dotnet build -p:RootNamespace=Acme
Titles: Dune, Emma
Type name: Acme.Reports.Program
Assembly:  Props
Version:   1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: active (OverflowException)
Symbols:   DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community

Read the outputs against the properties. Namespace Reports in the code became Shelf.Build.Reports, and Acme.Reports when the root namespace changed: the Namespace statement never contains the root namespace. The assembly name is Props, from the file name, whatever the root namespace says. The informational version is the Version value with the + and commit hash that the SDK appends when source control information is present; the code strips that suffix before printing. Release removed DEBUG and TRACE stayed. RemoveIntegerChecks turned Integer.MaxValue + 1 from an OverflowException into a silent wrap to -2147483648: a program whose output depends on a project property needs the property in its documentation. OptionCompare Text made "a" = "A" true for the whole project, which a file-level Option Compare Binary would override again. Built and run: 1 project, 5 builds.

Project settings that break or change the build

Each of the following projects is the default Exe project for net10.0 with root namespace E plus the one line shown. The lines printed are the diagnostics of dotnet build (the Build succeeded summary is omitted). Several are errors that only appear because a project property was set; they are the messages you will meet when you change a setting in an existing solution.

OptionStrict in the project file

<OptionStrict>On</OptionStrict>
Public Module Program
    Public Sub Main()
        Dim price As Decimal = 12.5D
        Dim whole As Integer = price
        Dim anything As Object = whole
        Console.WriteLine(anything.Length)
    End Sub
End Module
Program.vb(4,32): error BC30512: Option Strict On disallows implicit conversions from 'Decimal' to 'Integer'.
Program.vb(6,27): error BC30574: Option Strict On disallows late binding.

The same two statements build with 0 warnings and 0 errors under the SDK default (OptionStrict Off, which vbc receives as /optionstrict:custom), and the late-bound anything.Length is only found at run time: run in that default project, the narrowing silently rounded 3.5 to 4 and the late-bound Length call on an Integer threw MissingMemberException. This is the whole case for setting OptionStrict in Directory.Build.props, as the Shelf solution does. The error list of Option Strict On is on Option Statements.

OptionExplicit

Public Module Program
    Public Sub Main()
        total = 3
        Console.WriteLine(total)
    End Sub
End Module
Program.vb(3,9): error BC30451: 'total' is not declared. It may be inaccessible due to its protection level.
Program.vb(4,27): error BC30451: 'total' is not declared. It may be inaccessible due to its protection level.

With <OptionExplicit>Off</OptionExplicit> the same file builds with 0 warnings and 0 errors, and total becomes an implicitly declared Object. Nothing in a modern project has a reason to switch it off. A file can still say Option Explicit Off, which wins over the project.

Implicit imports switched off

DisableImplicitNamespaceImports set to true removes the SDK’s eight Import items (all of Microsoft.VisualBasic, System, System.Collections, System.Collections.Generic, System.Diagnostics, System.Linq, System.Xml.Linq, System.Threading.Tasks):

Public Module Program
    Public Sub Main()
        Dim names As New List(Of String)
        Console.WriteLine(names.Count)
    End Sub
End Module
Program.vb(3,26): error BC30002: Type 'List' is not defined.
Program.vb(4,9): error BC30451: 'Console' is not declared. It may be inaccessible due to its protection level.

Every file now needs its own Imports System and Imports System.Collections.Generic. This is the setting to use when you want a project where nothing is imported without being written; the price is that Microsoft.VisualBasic is no longer imported either: Left("abc", 2) then fails with error BC30451: 'Left' is not declared. It may be inaccessible due to its protection level. until the file says Imports Microsoft.VisualBasic. Keywords such as CInt are part of the language and keep working.

LangVersion

<LangVersion>14</LangVersion>
Public Module Program
    Public Sub Main()
        Dim pair = (Title:="Dune", Year:=1965)
        Dim big As Integer = 1_000_000
        Console.WriteLine(pair.Title & " " & big.ToString())
    End Sub
End Module
Program.vb(3,20): error BC36716: Visual Basic 14.0 does not support tuples.
Program.vb(4,30): error BC36716: Visual Basic 14.0 does not support digit separators.

With <LangVersion>15</LangVersion> the same file built with 0 warnings and 0 errors. With a value that does not exist (17.14) the compiler stops before reading any source: vbc : error BC2014: the value '17.14' is invalid for option 'langversion'.

Two entry points and StartupObject

Public Module First
    Public Sub Main()
        Console.WriteLine("First")
    End Sub
End Module

Public Module Second
    Public Sub Main()
        Console.WriteLine("Second")
    End Sub
End Module
vbc : error BC30738: 'Sub Main' is declared more than once in 'E': E.First.Main(), E.Second.Main()

With <StartupObject>E.Second</StartupObject> (the root namespace E plus the module name; without the root namespace prefix the name is not found) the program built and printed Second.

Warnings policy

One file with an unused local variable:

Option Strict On

Public Module Program
    Public Sub Main()
        Dim unused As Integer
        Console.WriteLine("hello")
    End Sub
End Module
Project line(s) Result

(none)

Program.vb(5,13): warning BC42024: Unused local variable: 'unused'.

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

Program.vb(5,13): error BC42024: Unused local variable: 'unused'.

TreatWarningsAsErrors true and <WarningsNotAsErrors>BC42024</WarningsNotAsErrors>

Program.vb(5,13): warning BC42024: ... (still a warning; build succeeds)

<WarningsAsErrors>$(WarningsAsErrors);BC42024</WarningsAsErrors>

Program.vb(5,13): error BC42024: ...

<NoWarn>$(NoWarn);BC42024</NoWarn> and TreatWarningsAsErrors true

nothing: the warning is suppressed before it can be escalated

The NoWarn trap

The SDK’s default NoWarn hides BC42016 to BC42022, the warnings of the custom Option Strict mode. Assigning NoWarn without $(NoWarn) throws that list away and makes those warnings appear in every file of the project:

Public Module Program
    Public Sub Main()
        Dim count As Integer = 3.5
        Console.WriteLine(count)
    End Sub
End Module
<NoWarn>BC42024</NoWarn>
Program.vb(3,32): warning BC42016: Implicit conversion from 'Double' to 'Integer'.

With the default NoWarn the same file builds silently (and prints 4: the narrowing is real, only the warning is hidden). Write <NoWarn>$(NoWarn);BC42024</NoWarn> to append. A project that sets OptionStrict to On is not exposed to this trap, because there is nothing left to warn about.

OptionStrict is a Boolean in MSBuild

The Vbc task declares OptionStrict as a Boolean, so the third state of the language statement (Option Strict Custom does not exist as a statement either) is not available as a project value:

<OptionStrict>Custom</OptionStrict>
Microsoft.VisualBasic.Core.targets(103,10): error MSB4030: "Custom" is an invalid value for the "OptionStrict" parameter of the "Vbc" task. The "OptionStrict" parameter is of type "System.Boolean".

Off is what already gives the custom mode, as the command lines above show.

DefineConstants separators

DefineConstants is passed to /define, which separates entries by commas. The semicolon that MSBuild uses elsewhere (and that C# projects use for this very property) breaks the build:

<DefineConstants>ALPHA=1;BETA=True</DefineConstants>
vbc : error BC31030: Conditional compilation constant 'ALPHA=1 ^^ ^^ ;BETA=True' is not valid: End of statement expected.

Appending with $(DefineConstants);ALPHA=1;BETA=True fails the same way (BC31030, "Identifier expected" for the empty first entry). Both of

<DefineConstants>ALPHA=1,BETA=True</DefineConstants>
<DefineConstants>$(DefineConstants),ALPHA=1,BETA=True</DefineConstants>

built, and a program using #If ALPHA = 1, #If BETA and #If DEBUG printed ALPHA is 1, BETA is on, DEBUG is on. The ordering of the options the task writes (a ,, empty entry for an empty $(DefineConstants)) is harmless. Built and run: 19 small projects in this section, plus 4 variants of the DefineConstants project.

LangVersion

LangVersion limits the compiler to the syntax of an older Visual Basic version and nothing else: it does not change the target framework, the library surface or the runtime behaviour. It becomes /langversion:<value>. The compiler itself lists what it accepts:

$ vbc -langversion:?
Supported language versions:
default
9
10
11
12
14
15
15.3
15.5
16
16.9
17.13 (default)
latest

Note that the list ends at 17.13 and has no 17 or 17.0. Each candidate value was passed to the real compiler:

Value Result

7, 8, 13

error BC2014: the value '7' is invalid for option 'langversion' (the same text for the others)

9, 9.0

accepted (9 is the oldest language version this compiler accepts)

10, 11, 12, 14, 14.0, 15, 15.0, 15.3, 15.5, 16, 16.0, 16.9, 17.13

accepted (a whole number may be written with .0)

default, latest, Latest

accepted; both mean 17.13 on this SDK; the value is not case sensitive

17, 17.0, 17.9, 17.14, 18, preview

error BC2014: the value '17.14' is invalid for option 'langversion' (the same text for the others)

default means the latest major version and latest the latest version including minor versions; they are the same today because the last one is a minor version of 17. The compiler -help text says the same. VB 17.0 added consumption of CallerArgumentExpression and 17.13 honours OverloadResolutionPriority and the unmanaged constraint, but neither is a syntax change, which is why neither has a LangVersion value of its own other than the final 17.13.

To see which syntax each version gates, ten probes (one tiny library each) were compiled with each value. The cell is ok, or the first error code the compiler reported (BC36716 is "Visual Basic N does not support feature", BC30241 is the named-argument error):

Probe 9 10 11 12 14 15 15.3 15.5 16 16.9 latest

Auto-implemented property Public Property X As Integer

BC36716

ok

ok

ok

ok

ok

ok

ok

ok

ok

ok

Async Function with Await

BC36716

BC36716

ok

ok

ok

ok

ok

ok

ok

ok

ok

Iterator Function with Yield

BC36716

BC36716

ok

ok

ok

ok

ok

ok

ok

ok

ok

String interpolation $"n = {n}"

BC36716

BC36716

BC36716

BC36716

ok

ok

ok

ok

ok

ok

ok

Null-conditional s?.Length

BC36716

BC36716

BC36716

BC36716

ok

ok

ok

ok

ok

ok

ok

Tuple literal (1, 2)

BC36716

BC36716

BC36716

BC36716

BC36716

ok

ok

ok

ok

ok

ok

Digit separator 1_000

BC36716

BC36716

BC36716

BC36716

BC36716

ok

ok

ok

ok

ok

ok

Tuple name inference (a, b) then t.a

BC36716

BC36716

BC36716

BC36716

BC36716

ok

ok

ok

ok

ok

ok

Named argument before a positional one G(a:=1, 2)

BC30241

BC30241

BC30241

BC30241

BC30241

BC30241

BC30241

ok

ok

ok

ok

Private Protected

BC36716

BC36716

BC36716

BC36716

BC36716

BC36716

BC36716

ok

ok

ok

ok

The gates match the history of the language and the Learn what’s-new page: auto-implemented properties arrived with 10, Async and iterators with 11, interpolation and ?. with 14, tuples and digit separators with 15, non-trailing named arguments and Private Protected with 15.5 (the compiler says BC30241 for the first before 15.5). A library that must compile for an old consumer’s language version can therefore pin LangVersion and get an error on any newer syntax. Nothing in the language added syntax after 15.5 apart from places where a comment is now allowed (16.0); the later versions (16.9, 17.0, 17.13) are consumption features: init-only properties, required members, OverloadResolutionPriority and CallerArgumentExpression need no new syntax. See History, Versions and Language Strategy for the story.

Where the documentation lags: the Learn -langversion page and the "Select the Visual Basic language version" page list values only up to 16.9, and the -langversion page says the option can be set "only by using the command line" (its sibling page describes the project property and Directory.Build.props, which is the one that is right: <LangVersion> in the project file produces the option, as the 14 example above showed, and the compiler accepts 17.13). Built and run: 10 probes by 11 versions, plus the value list.

Global imports and the missing global using

C# 10 introduced global using and ImplicitUsings. Visual Basic had global imports first, and they are an item of the project, not a statement of the language: <Import Include="System.Text" /> becomes an entry in /imports:. There is no Global Imports statement; each file still declares its own Imports. The SDK adds eight Import items for you (the eight namespaces in the table above; System.Data is the ninth when the target is .NET Framework). The VB templates add more:

Template Import items written into the .vbproj

console, classlib, mstest, xunit, nunit

none (the SDK ones apply)

winforms

System.Data, System.Drawing, System.Windows.Forms

wpf

System.Windows, System.Windows.Controls, System.Windows.Data, System.Windows.Documents, System.Windows.Input, System.Windows.Media, System.Windows.Media.Imaging, System.Windows.Navigation, System.Windows.Shapes

The Import item in a project must not be confused with MSBuild’s own <Import Project="..." /> element, which pulls in another project file. They look alike and are unrelated. Two further behaviours follow from the tests above:

  • Setting UseWindowsForms or UseWPF does not add Import items; the template does. If you create the project by hand, add them yourself.

  • The VB mstest template still writes a C#-style <Using Include="..." /> item that does nothing in VB (see the table of properties that do nothing). The tests use an ordinary Imports statement in the file.

An alias is possible in a file (Imports Sb = System.Text.StringBuilder) but not as a project-wide import. Because the compiler imports nothing implicitly beyond this list, a type of another namespace (StringBuilder, Regex, HttpClient) always needs a file-level Imports or an Import item, exactly as in the failing ImplicitUsings example above.

DefineConstants and the implicit symbols

The symbols in /define: are the union of what you put in DefineConstants and what the SDK adds. Here are all the implicit ones observed, with the project that produced them:

Symbol Value or meaning Source

CONFIG

String: "Debug", "Release" or the custom configuration name

Configuration

DEBUG, TRACE

-1 (true) when defined

DefineDebug / DefineTrace: Debug has both, Release has TRACE only, a custom configuration has neither

RELEASE, STAGING, …​ (the configuration name in upper case, dashes and dots become underscores)

-1

ImplicitConfigurationDefine; off with DisableImplicitConfigurationDefines

PLATFORM

String: "AnyCPU"

Platform

_MyType

String: "Empty" on .NET (when MyType is empty), "Console", "Windows", "WindowsForms", …​

MyType

NET, NET10_0, NETCOREAPP

-1

net10.0 (NET8_0 for net8.0)

NET5_0_OR_GREATER to NET10_0_OR_GREATER, NETCOREAPP1_0_OR_GREATER to NETCOREAPP3_1_OR_GREATER

-1

net10.0: every version up to the target

NETFRAMEWORK, NET481

-1

net481

NETSTANDARD, NETSTANDARD2_0

-1

netstandard2.0

WINDOWS, WINDOWS7_0, WINDOWS7_0_OR_GREATER

-1

net10.0-windows

VBC_VER

Number: 17.13 here

Defined by the compiler, not by MSBuild; it does not appear on the command line

A program that tests the compiler-provided and implicit symbols and ran in a default net10.0 console project printed:

VBC_VER is 17.13 or later
_MyType is Empty
PLATFORM is AnyCPU
NET10_0
not NETFRAMEWORK

(the code is #If VBC_VER >= 17.13 Then, #If _MyType = "Empty" Then, #If PLATFORM = "AnyCPU" Then, #If NET10_0 Then, #If NETFRAMEWORK Then with #Else.) Comparing a string symbol with a string literal works in #If. The #If directive, #Const and the Conditional attribute are on Conditional Compilation and Directives. The three gotchas of the property itself:

  • The separator is a comma (shown above); a semicolon fails with BC31030.

  • A string symbol is written with plain double quotes in the element (EDITION="Community", as in the demonstration above); the generated command line escapes them.

  • DefineDebug false does not remove DEBUG (see the Configuration table).

MyType and the Visual Basic runtime

Two groups of settings decide what the Microsoft.VisualBasic library looks like to the program. MyType selects the My classes the compiler generates; VBRuntime selects which Microsoft.VisualBasic is linked. The SDK’s own comment in Microsoft.NET.Sdk.VisualBasic.targets states the defaults, and the captured command lines agree with it:

Target /vbruntime option observed Why

net10.0, net8.0

/vbruntime:<reference pack>/Microsoft.VisualBasic.dll

UseReferencedVBRuntime is true for .NET Core 3.0 and later: the runtime is the Microsoft.VisualBasic among the references.

net10.0-windows with UseWindowsForms

/vbruntime:.../microsoft.windowsdesktop.app.ref/10.0.13/ref/net10.0/Microsoft.VisualBasic.dll

The Windows Desktop reference pack contains the runtime with the Windows Forms application services.

net481

/vbruntime:.../microsoft.netframework.referenceassemblies.net481/1.0.3/build/.NETFramework/v4.8.1/Microsoft.VisualBasic.dll

The .NET Framework’s own Microsoft.VisualBasic.dll, found in the reference assemblies package that the SDK downloads, which is why net481 builds even on Linux.

netstandard2.0

/vbruntime*

VBRuntime Embed: the runtime is compiled into your assembly, because no Microsoft.VisualBasic.dll is available for netstandard.

For a library that targets netstandard2.0 the embedded runtime means the library carries its own copy of the Visual Basic runtime functions it uses, and some runtime functions are not available to it (the effects are on Libraries, Packaging and Deployment). The properties are VBRuntime (a path, Embed or Default), UseReferencedVBRuntime and NoVBRuntimeReference; MyType is a plain string whose values include Console, Windows, WindowsForms, WindowsFormsWithCustomSubMain, Web, Empty and Custom. On .NET 10, Console fails with BC30002 ("Type …​ is not defined") and Web does not build at all; Empty is the default and the right choice for console programs and libraries. WindowsFormsWithCustomSubMain is for Windows Forms projects that write their own Sub Main, as the template does. The My namespace and what each value generates are on The My Namespace.

Multi-targeting

A library that must serve .NET Framework and .NET consumers lists several target frameworks in TargetFrameworks and uses the framework symbols in #If. The captured command lines for one project differing only in -p:TargetFramework:

Option net10.0 net8.0 net481 netstandard2.0

/imports

eight namespaces

eight namespaces

nine (adds System.Data)

eight namespaces

/nowarn

SDK list plus BC41997

same

SDK list only (the SDK adds BC41997 for .NET targets)

SDK list only

/vbruntime

reference pack file

the microsoft.netcore.app.ref 8.0 pack downloaded to the NuGet cache

NuGet cache microsoft.netframework.referenceassemblies.net481

* (embedded)

/define (extra symbols)

_MyType="Empty", NET, NET10_0, NETCOREAPP, NETnn_0_OR_GREATER

_MyType="Empty", NET, NET8_0, NETCOREAPP, …​

NETFRAMEWORK, NET481 (no _MyType)

_MyType="Empty", NETSTANDARD, NETSTANDARD2_0

/refout

yes

yes

no

no

/subsystemversion

no

no

6.00

no

/warnaserror+: list

NU1605,SYSLIB0011

same

NU1605

NU1605

A complete example is a library Lib that targets net10.0;net481, with Platform.Describe returning a different string per target through the symbols, and a net10.0 console program that references it. The project of the library:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net10.0;net481</TargetFrameworks>
    <RootNamespace>Shelf.Lib</RootNamespace>
    <OptionStrict>On</OptionStrict>
    <NuGetAudit>false</NuGetAudit>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net481'">
    <Reference Include="System.Net.Http" />
  </ItemGroup>

</Project>
Option Strict On

Public NotInheritable Class Platform

    Private Sub New()
    End Sub

    Public Shared Function Describe() As String
#If NETFRAMEWORK Then
        Return ".NET Framework build (NET481 defined)"
#ElseIf NET10_0_OR_GREATER Then
        Return ".NET 10 build (NET10_0_OR_GREATER defined)"
#Else
        Return "other build"
#End If
    End Function

End Class
$ dotnet build App
  Lib -> Lib/bin/Debug/net10.0/Lib.dll
  App -> App/bin/Debug/net10.0/App.dll
$ dotnet run --project App --no-build
.NET 10 build (NET10_0_OR_GREATER defined)
$ dotnet build Lib -f net481
  Lib -> Lib/bin/Debug/net481/Lib.dll

A consumer that targets net10.0 makes MSBuild build only the matching target of the library, so bin/Debug held only net10.0 after the first build. dotnet build Lib -f net481 built the other one, on Linux, using the reference assemblies package. A net481 build can be compiled anywhere but run only on Windows. The conditional Reference item is the usual way to add an assembly that only the .NET Framework target needs. Built and run: 3 projects (the net481 output compiled, not run).

Sharing settings across a solution

Every setting above belongs in the project file if it is specific to the project and in one of four solution-level files if it is shared. MSBuild looks upward from each project directory, and the first file of each kind it finds is imported automatically: Directory.Build.props before the project body (so project files can override it), Directory.Build.targets after the SDK targets (so it can override them), Directory.Packages.props for central package versions, and global.json (read by the dotnet host) for the SDK version. The buildprops, buildtargets, packagesprops, globaljson, editorconfig, nugetconfig and gitignore short names of dotnet new create starters for them.

The experiment: a repository with Directory.Build.props (sets TargetFramework, OptionStrict On, LangVersion, TreatWarningsAsErrors, Deterministic, Version and ManagePackageVersionsCentrally), Directory.Build.targets (prints the effective values after every build), Directory.Packages.props (one PackageVersion) and global.json; and two projects, src/Core that adds nothing and src/Tools that sets OptionStrict Off and Version 0.1.0.

<!-- Directory.Build.props -->
<Project>

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <OptionStrict>On</OptionStrict>
    <LangVersion>latest</LangVersion>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <Deterministic>true</Deterministic>
    <Version>2.0.0</Version>
    <NuGetAudit>false</NuGetAudit>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>

</Project>

<!-- Directory.Build.targets -->
<Project>

  <Target Name="ShowOptions" AfterTargets="Build">
    <Message Importance="high" Text="$(MSBuildProjectName): OptionStrict=$(OptionStrict) Version=$(Version) RootNamespace=$(RootNamespace)" />
  </Target>

</Project>

<!-- Directory.Packages.props -->
<Project>

  <ItemGroup>
    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
  </ItemGroup>

</Project>

<!-- global.json -->
{ "sdk": { "version": "10.0.100", "rollForward": "latestFeature" } }

<!-- src/Core/Core.vbproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <RootNamespace>Shelf.Core</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
  </ItemGroup>

</Project>

<!-- src/Tools/Tools.vbproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <RootNamespace>Shelf.Tools</RootNamespace>
    <OptionStrict>Off</OptionStrict>
    <Version>0.1.0</Version>
  </PropertyGroup>

</Project>

(The PackageReference in Core.vbproj has no Version: with central package management the version comes from Directory.Packages.props.)

$ dotnet build src/Core
  Core: OptionStrict=On Version=2.0.0 RootNamespace=Shelf.Core
$ dotnet build src/Tools
  Tools: OptionStrict=Off Version=0.1.0 RootNamespace=Shelf.Tools
$ dotnet msbuild src/Core/Core.vbproj -getProperty:OptionStrict,TreatWarningsAsErrors,Version
OptionStrict: On   TreatWarningsAsErrors: true   Version: 2.0.0
$ dotnet msbuild src/Tools/Tools.vbproj -getProperty:OptionStrict,TreatWarningsAsErrors,Version
OptionStrict: Off  TreatWarningsAsErrors: true   Version: 0.1.0
$ dotnet msbuild src/Tools/Tools.vbproj -p:OptionStrict=On -getProperty:OptionStrict
On
$ dotnet --version
10.0.402
$ dotnet list src/Core package
   Top-level Package                                Requested   Resolved
   > Microsoft.Extensions.Logging.Abstractions      10.0.0      10.0.0

(-getProperty prints JSON; the two JSON objects are shown as one line each.) The project value won over Directory.Build.props (Tools kept Off and 0.1.0); the command-line -p:OptionStrict=On won over the project; global.json asked for 10.0.100 with latestFeature and the installed 10.0.402 satisfied it. The central package version 10.0.0 was applied to a PackageReference without a Version. Built and run: 2 projects.

Nested files. Only the nearest Directory.Build.props is imported. A file deeper in the tree that wants the parent’s settings imports it explicitly, which is what a tests folder normally does. This was run with tests/Directory.Build.props:

<Project>

  <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

  <PropertyGroup>
    <IsPackable>false</IsPackable>
    <WarningsNotAsErrors>$(WarningsNotAsErrors);BC42024</WarningsNotAsErrors>
  </PropertyGroup>

  <ItemGroup>
    <Import Include="System.Text" />
  </ItemGroup>

</Project>

The test project got the parent’s OptionStrict On, TreatWarningsAsErrors true and Company Shelf, plus IsPackable false, WarningsNotAsErrors ;BC42024 and a ninth Import item System.Text; the project in src/ got only the parent’s values and IsPackable true. Built and run: 2 projects.

The Shelf reference solution of this site sets OptionStrict, TreatWarningsAsErrors and NuGetAudit in one Directory.Build.props, which is what you should do for any multi-project Visual Basic repository: it makes OptionStrict On a solution policy instead of a per-file chore. The .editorconfig keys for style and analyzer severity are on XML Documentation, Analyzers and Code Style.

The dotnet command line for Visual Basic

Everything in an IDE is available from the dotnet command, and the command is what continuous integration runs (see Testing for CI). The default language of dotnet new is C#; a Visual Basic project needs -lang VB (or --language VB). The templates that have a Visual Basic version on SDK 10.0.402 (dotnet new list --language VB):

Template Name                  Short Name           Language  Tags
-----------------------------  -------------------  --------  -----------------------
Class Library                  classlib             C#,F#,VB  Common/Library
Console App                    console              C#,F#,VB  Common/Console
MSTest Test Class              mstest-class         C#,F#,VB  Test/MSTest
MSTest Test Project            mstest               C#,F#,VB  Test/MSTest/Desktop/Web
NUnit Test Item                nunit-test           C#,F#,VB  Test/NUnit
NUnit Test Project             nunit                C#,F#,VB  Test/NUnit/Desktop/Web
Windows Forms App              winforms             C#,VB     Common/WinForms
Windows Forms Class Library    winformslib          C#,VB     Common/WinForms
Windows Forms Control Library  winformscontrollib   C#,VB     Common/WinForms
WPF Application                wpf                  C#,VB     Common/WPF
WPF Class Library              wpflib               C#,VB     Common/WPF
WPF Custom Control Library     wpfcustomcontrollib  C#,VB     Common/WPF
WPF User Control Library       wpfusercontrollib    C#,VB     Common/WPF
xUnit Test Project             xunit                C#,F#,VB  Test/xUnit/Desktop/Web

There is no Visual Basic webapi, web, blazor, worker, grpc or MAUI template; the list above is the complete VB set. The configuration-file templates (sln, slnf, globaljson, editorconfig, nugetconfig, gitignore, gitattributes, tool-manifest, buildprops, buildtargets, packagesprops, webconfig) have no language and apply to any project. The console template’s own options show how little VB-specific there is: -f/--framework (one choice, net10.0), --langVersion (writes LangVersion), --no-restore and --aot.

A complete workflow

The following session builds the Shelf skeleton used by the other pages: a library, a console program that uses it and an MSTest project. The commands are the real ones; the Directory.Build.props was written first (it sets OptionStrict On, TreatWarningsAsErrors true and NuGetAudit false, see above). First the scaffolding:

dotnet new sln -n Shelf
dotnet new classlib -lang VB -o Shelf.Core
dotnet new console -lang VB -o Shelf.Cli
dotnet new mstest -lang VB -o Shelf.Tests
dotnet sln add Shelf.Core Shelf.Cli Shelf.Tests
dotnet add Shelf.Cli reference Shelf.Core
dotnet add Shelf.Tests reference Shelf.Core
dotnet add Shelf.Core package Microsoft.Extensions.Logging.Abstractions --version 10.0.0
dotnet sln list
dotnet list Shelf.Cli reference
dotnet list Shelf.Core package
Project `Shelf.Core/Shelf.Core.vbproj` added to the solution.
Project `Shelf.Cli/Shelf.Cli.vbproj` added to the solution.
Project `Shelf.Tests/Shelf.Tests.vbproj` added to the solution.
Reference `..\Shelf.Core\Shelf.Core.vbproj` added to the project.
Reference `..\Shelf.Core\Shelf.Core.vbproj` added to the project.
info : PackageReference for package 'Microsoft.Extensions.Logging.Abstractions' version '10.0.0' added to file '/w/work/Shelf.Core/Shelf.Core.vbproj'.
Project(s)
----------
Shelf.Cli/Shelf.Cli.vbproj
Shelf.Core/Shelf.Core.vbproj
Shelf.Tests/Shelf.Tests.vbproj
Project reference(s)
--------------------
../Shelf.Core/Shelf.Core.vbproj
   [net10.0]:
   Top-level Package                                Requested   Resolved
   > Microsoft.Extensions.Logging.Abstractions      10.0.0      10.0.0

(Some chatty lines of the restore were left out.) dotnet new sln wrote Shelf.slnx, the XML solution format that is the default in SDK 10; its whole content is three Project Path elements. Existing .sln files keep working and dotnet sln Old.sln migrate converted one: .slnx file /w/t/Old.slnx generated. Note that the dotnet add reference command put the ProjectReference in a new ItemGroup above the PropertyGroup, which is harmless: MSBuild does not care about the order of groups in the project body.

The dotnet new mstest project built and ran straight from the template under OptionStrict On and TreatWarningsAsErrors (Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1), so the C#-style properties it carries are inert. Then the sources of the three projects were replaced:

Option Strict On

Namespace Domain

    Public NotInheritable Class Isbn

        Public ReadOnly Property Value As String

        Public Sub New(value As String)
            If Not IsValid(value) Then
                Throw New ArgumentException("Not a valid ISBN-13: " & value, NameOf(value))
            End If
            Me.Value = value
        End Sub

        Public Shared Function IsValid(value As String) As Boolean
            If value Is Nothing OrElse value.Length <> 13 Then
                Return False
            End If
            Dim sum As Integer = 0
            For i As Integer = 0 To 12
                If Not Char.IsDigit(value(i)) Then
                    Return False
                End If
                Dim digit As Integer = Asc(value(i)) - Asc("0"c)
                sum += If(i Mod 2 = 0, digit, digit * 3)
            Next
            Return sum Mod 10 = 0
        End Function

    End Class

End Namespace
Option Strict On

Imports Shelf.Core.Domain

Public Module Program

    Public Function Main(args As String()) As Integer
        Dim failures As Integer = 0
        For Each candidate As String In args
            If Isbn.IsValid(candidate) Then
                Console.WriteLine(candidate & " is a valid ISBN-13")
            Else
                Console.WriteLine(candidate & " is not valid")
                failures += 1
            End If
        Next
        Return failures
    End Function

End Module
Option Strict On

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports Shelf.Core.Domain

<TestClass>
Public Class IsbnTests

    <TestMethod>
    Public Sub Valid_isbn_is_accepted()
        Assert.IsTrue(Isbn.IsValid("9780306406157"))
    End Sub

    <TestMethod>
    Public Sub Wrong_check_digit_is_rejected()
        Assert.IsFalse(Isbn.IsValid("9780306406158"))
    End Sub

End Class

(The root namespaces are the project names, Shelf.Core, Shelf.Cli and Shelf.Tests, so Namespace Domain in the library is Shelf.Core.Domain; the console module and the test class sit in their project’s root namespace and need no Namespace block.) The commands that use them:

dotnet build Shelf.slnx
dotnet run --project Shelf.Cli --no-build -- 9780306406157 123
dotnet test Shelf.Tests
dotnet pack Shelf.Core -c Release -o nupkgs
dotnet publish Shelf.Cli -c Release -o pub
dotnet pub/Shelf.Cli.dll 9780306406157
dotnet format Shelf.slnx --verify-no-changes
dotnet clean Shelf.slnx
dotnet test Shelf.Tests --list-tests
dotnet msbuild Shelf.Cli -pp:expanded.xml
  Shelf.Core -> /w/work/Shelf.Core/bin/Debug/net10.0/Shelf.Core.dll
  Shelf.Tests -> /w/work/Shelf.Tests/bin/Debug/net10.0/Shelf.Tests.dll
  Shelf.Cli -> /w/work/Shelf.Cli/bin/Debug/net10.0/Shelf.Cli.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)
9780306406157 is a valid ISBN-13
123 is not valid
exit code: 1
Passed!  - Failed:     0, Passed:     2, Skipped:     0, Total:     2, Duration: 8 ms - Shelf.Tests.dll (net10.0)
  The package Shelf.Core.1.0.0 is missing a readme. Go to https://aka.ms/nuget/authoring-best-practices/readme to learn why package readmes are important.
  Successfully created package '/w/work/nupkgs/Shelf.Core.1.0.0.nupkg'.
  Shelf.Cli -> /w/work/pub/
Shelf.Cli
Shelf.Cli.deps.json
Shelf.Cli.dll
Shelf.Cli.pdb
Shelf.Cli.runtimeconfig.json
Shelf.Core.dll
Shelf.Core.pdb
9780306406157 is a valid ISBN-13
The following Tests are available:
    Valid_isbn_is_accepted
    Wrong_check_digit_is_rejected

(exit code: 1 was added by the script after dotnet run; it is the process exit code of the program, the count of invalid ISBNs, which dotnet run passes through. The ls pub listing is the second block of names: the apphost Shelf.Cli, its .deps.json and .runtimeconfig.json, the two assemblies and their PDBs. dotnet format --verify-no-changes and dotnet clean printed nothing of note for this solution.) -pp:expanded.xml produced a 1.5 MB file for the console project in which Directory.Build.props (line 94) comes long before the Visual Basic .props (line 1090) and the project body, which is why a project can override what the file sets. Built and run: 3 projects, 2 tests.

What each command does to the MSBuild model

Command MSBuild equivalent Visual Basic notes

dotnet restore

msbuild -t:Restore

Reads PackageReference, ProjectReference and Directory.Packages.props and writes obj/project.assets.json; build, run, test do it first unless --no-restore. Offline builds need the packages already in the NuGet cache.

dotnet build [-c Release] [-o dir] [-f tfm] [--no-restore] [--no-incremental] [-p:Name=Value] [-v:level] [-bl]

msbuild -t:Build

-p: sets a global property that beats the project file (shown above); -f selects one target of a multi-targeted project; -bl writes msbuild.binlog, readable with the MSBuild Structured Log Viewer, the best tool for "why did this property get that value" questions.

dotnet run [--project p] [-c cfg] [--no-build] [-- args]

build, then dotnet App.dll

Everything after -- goes to Main. The program’s return value is the exit code. A stale build after -p:StartupObject=... is a known trap: use dotnet build --no-incremental and then run.

dotnet test [project] [--filter ...]

build, then the test host

The VB mstest, xunit and nunit templates; see Testing for the runner differences. In this SDK the argument is a project path; a --solution switch is not accepted. --list-tests lists the test names without running them.

dotnet pack, dotnet publish

msbuild -t:Pack, -t:Publish

pack makes a .nupkg (a warning about the missing readme is normal); publish creates the deployable folder. Publish modes are on Libraries, Packaging and Deployment.

dotnet clean

msbuild -t:Clean

Deletes the outputs of the build.

dotnet sln add / remove / list / migrate

(solution file edit)

Works on .sln and .slnx; migrate writes the .slnx.

dotnet add reference / package, dotnet remove ..., dotnet list ...

(project file edit)

Edits the .vbproj; dotnet add package also restores (its output is the info : lines shown above).

dotnet format

(Roslyn workspace, not MSBuild targets)

Applies .editorconfig formatting and style rules; it supports Visual Basic.

dotnet msbuild

the same MSBuild engine

-getProperty:A,B, -getItem:Name and -getTargetResult:Target print values as JSON without building; -pp:out.xml writes the fully expanded project, with every imported file inlined in evaluation order, which is the best way to read the SDK’s contribution.

dotnet watch

(file watcher around run/test/build)

Hot Reload for VB: the VB targets add the SupportsHotReload capability for projects targeting .NET 6 or later (it is in Microsoft.NET.Sdk.VisualBasic.targets). Not run here; see Debugging and Diagnostics.

Two useful orientation commands: dotnet --list-sdks printed 10.0.402 [/usr/share/dotnet/sdk], and dotnet msbuild -getProperty:MSBuildVersion,NETCoreSdkVersion,VisualBasicTargetsPath,VisualBasicCoreTargetsPath printed 18.9.15, 10.0.402, /usr/share/dotnet/sdk/10.0.402/Microsoft.VisualBasic.CurrentVersion.targets and .../Sdks/Microsoft.NET.Sdk/targets/../../../Roslyn/Microsoft.VisualBasic.Core.targets: the last two are the files that do the work. Built and run: all of the above.

The vbc option reference

vbc is the Roslyn Visual Basic compiler. In the .NET SDK it is Roslyn/bincore/vbc.dll under the SDK folder (/usr/share/dotnet/sdk/10.0.402/Roslyn/bincore/vbc.dll in the container), a framework-dependent program started with dotnet; the vbc file next to it is its apphost. vbc -version printed 5.9.0-1.26505.111: that is the compiler version, not the language version (17.13, see above). Learn’s "compiler options listed by category" page and the individual option pages describe an older compiler; the table below is the output of vbc -help of this compiler, in its eight groups, plus five options the compiler accepts but does not list. The Learn page of an option (where one exists) is linked in the references. In the "Option" column [+|-] means the option may be followed by + (on), - (off) or nothing (on).

Each option below was passed to the SDK 10.0.402 compiler on a small module. All were accepted; the ones that take a file reported a problem with the (deliberately missing) file instead of "unrecognized option", which proves the option itself was understood. A made-up option produces warning BC2007 and is ignored: vbc : warning BC2007: unrecognized option '-bogusoption'; ignored. That is also why a misspelled switch in a response file does not stop a build, which is worth knowing when you read a build log.

Output file

Option Short Purpose MSBuild counterpart

-out:<file>

Name of the output file.

AssemblyName and the intermediate path (obj/<config>/<tfm>/Name.dll)

-target:exe

-t

Console application (the compiler default).

OutputType Exe

-target:winexe

-t

Windows (GUI) application: no console is attached.

OutputType WinExe

-target:library

-t

Class library (.dll).

OutputType Library

-target:module

-t

A .netmodule that can be added to an assembly with -addmodule.

OutputType Module

-target:appcontainerexe

-t

Windows Runtime application that runs in an AppContainer.

OutputType AppContainerExe

-target:winmdobj

-t

Windows Metadata intermediate file.

OutputType WinMDObj

-doc[+|-]

Generate the XML documentation file.

GenerateDocumentationFile

-doc:<file>

Generate the XML documentation file to the given path.

DocumentationFile

-refout:<file>

Also write a reference assembly (metadata only) to the given path.

ProduceReferenceAssembly (default true)

Input files

Option Short Purpose MSBuild counterpart

-addmodule:<file_list>

Reference metadata from the given netmodules and make them part of the output assembly.

AddModules item

-link:<file_list>

-l

Embed the interop types of the given assembly instead of referencing it (no PIA needed at run time).

Reference item with EmbedInteropTypes true

-recurse:<wildcard>

Add the files matching the wildcard in the current directory and below. No MSBuild property: the Compile glob replaces it.

Compile items

-reference:<file_list>

-r

Reference the given assemblies.

Reference, ProjectReference, PackageReference, FrameworkReference items

-analyzer:<file_list>

-a

Run the analyzers (and source generators) in the given assemblies.

Analyzer items

-additionalfile:<file list>

Non-code files that analyzers and generators may read.

AdditionalFiles items

Resources

Option Short Purpose MSBuild counterpart

-linkresource:<resinfo>

-linkres

Link a file as an external assembly resource; resinfo is file[,name[,public|private]].

LinkResource items

-nowin32manifest

Do not embed the default Win32 manifest.

NoWin32Manifest

-resource:<resinfo>

-res

Embed the file as an assembly resource.

EmbeddedResource items

-win32icon:<file>

Icon (.ico) for the default Win32 resources.

ApplicationIcon

-win32manifest:<file>

Embed the given manifest.

Win32Manifest

-win32resource:<file>

Use the given compiled Win32 resource file (.res).

Win32Resource

Code generation

Option Short Purpose MSBuild counterpart

-optimize[+|-]

Enable optimizations.

Optimize

-removeintchecks[+|-]

Remove the integer overflow checks (default off).

RemoveIntegerChecks

-debug[+|-]

Emit debugging information.

DebugSymbols

-debug:full

Emit full debugging information (the help text calls it the default).

DebugType full

-debug:pdbonly

Emit full debugging information (the same text as full).

DebugType pdbonly

-debug:portable

Cross-platform PDB format; the SDK default.

DebugType portable

-debug:embedded

Cross-platform debugging information inside the assembly.

DebugType embedded

-deterministic

Produce a deterministic assembly (same input, same bytes). The task also writes -deterministic+ / -deterministic-.

Deterministic

-refonly

Produce a reference assembly in place of the main output.

ProduceOnlyReferenceAssembly

-instrument:TestCoverage

Produce an assembly instrumented to collect coverage information.

Instrument

-sourcelink:<file>

Source Link information to embed in the PDB (needs a PDB).

SourceLink

Errors and warnings

Option Short Purpose MSBuild counterpart

-nowarn

Disable all warnings.

WarningLevel 0

-nowarn:<number_list>

Disable the listed warnings (the BC prefix is optional).

NoWarn

-warnaserror[+|-]

Treat all warnings as errors.

TreatWarningsAsErrors

-warnaserror[+|-]:<number_list>

Treat the listed warnings as errors (+) or not (-).

WarningsAsErrors, WarningsNotAsErrors

-ruleset:<file>

A rule set file that disables or changes diagnostics.

CodeAnalysisRuleSet

-errorlog:<file>[,version=<sarif_version>]

Log all compiler and analyzer diagnostics in SARIF format; version 1 (default), 2 or 2.1 (both mean SARIF 2.1.0).

ErrorLog

-reportanalyzer

Report analyzer information such as execution time.

ReportAnalyzer

-skipanalyzers[+|-]

Skip the execution of diagnostic analyzers.

RunAnalyzers false

Language

Option Short Purpose MSBuild counterpart

-define:<symbol_list>

-d

Declare global conditional-compilation symbols (name=value,...).

DefineConstants (as FinalDefineConstants)

-imports:<import_list>

Declare global imports of namespaces from the referenced assemblies.

Import items

-langversion:?

Print the allowed language versions.

none

-langversion:<string>

Accept only the syntax of the given version: default, latest, or a number such as 14 or 15.3.

LangVersion

-optionexplicit[+|-]

Require explicit declaration of variables.

OptionExplicit

-optioninfer[+|-]

Allow local type inference.

OptionInfer

-rootnamespace:<string>

Root namespace of all type declarations.

RootNamespace

-optionstrict[+|-]

Enforce strict language semantics.

OptionStrict On and Off

-optionstrict:custom

Warn when strict semantics are not respected.

OptionStrict Off (the SDK passes this, not -optionstrict-)

-optioncompare:binary

Binary string comparisons (the default).

OptionCompare Binary

-optioncompare:text

Text-style (case-insensitive, culture-aware) string comparisons.

OptionCompare Text

Miscellaneous

Option Short Purpose MSBuild counterpart

-help

-?

Display the usage message.

none

-noconfig

Do not auto-include a VBC.RSP file. The task always passes it.

always set (NoConfig true)

-nologo

Do not display the compiler banner.

none observed in the captured command lines

-quiet

Quiet output mode.

VbcVerbosity quiet

-verbose

Display verbose messages (each referenced assembly was listed).

VbcVerbosity verbose

-parallel[+|-]

Concurrent build.

none: the Vbc task has no parameter for it

-version

Display the compiler version and exit (5.9.0-1.26505.111 on SDK 10.0.402).

none

Advanced

Option Short Purpose MSBuild counterpart

-baseaddress:<number>

Base address (hex) of a library or module.

BaseAddress

-checksumalgorithm:<alg>

Algorithm for the source checksums stored in the PDB: SHA1, SHA256 (default), SHA384, SHA512.

ChecksumAlgorithm

-codepage:<number>

Code page for opening source files that have no byte-order mark.

CodePage

-delaysign[+|-]

Delay-sign with only the public part of the key.

DelaySign

-publicsign[+|-]

Public-sign with only the public part of the key.

PublicSign

-errorreport:<string>

What to do after an internal compiler error: prompt, send, none or queue (default).

ErrorReport (the SDK passes prompt)

-generatedfilesout:<dir>

Folder for files generated during compilation (source generators).

CompilerGeneratedFilesOutputPath

-filealign:<number>

Alignment of the output file sections.

FileAlignment

-highentropyva[+|-]

Enable high-entropy ASLR.

HighEntropyVA

-keycontainer:<string>

Strong-name key container.

KeyContainerName

-keyfile:<file>

Strong-name key file.

KeyOriginatorFile (from AssemblyOriginatorKeyFile when SignAssembly is true)

-libpath:<path_list>

Directories (semicolon delimited) searched for metadata references.

AdditionalLibPaths

-main:<class>

-m

The class or module that contains Sub Main (or a Form).

StartupObject

-moduleassemblyname:<string>

Name of the assembly a module will be part of (only with -target:module).

ModuleAssemblyName

-netcf

Target the .NET Compact Framework.

TargetCompactFramework

-nostdlib

Do not reference the standard libraries automatically.

NoCompilerStandardLib (the SDK sets it)

-pathmap:<K1>=<V1>,<K2>=<V2>,...

Map source paths in compiler output.

PathMap

-platform:<string>

x86, x64, Itanium, arm, arm64, AnyCPU32BitPreferred or anycpu (default).

PlatformTarget, Prefer32Bit

-preferreduilang

Preferred language of compiler messages.

PreferredUILang

-nosdkpath

Do not search the default SDK path for standard library assemblies.

DisableSdkPath (the SDK sets it)

-reportivts[+|-]

Report all InternalsVisibleTo grants and annotate accessibility errors with where they came from.

ReportIVTs

-sdkpath:<path>

Path searched for standard library assemblies.

FrameworkPathOverride (when DisableSdkPath is not set)

-subsystemversion:<version>

Subsystem version of the output PE, number[.number].

SubsystemVersion

-utf8output[+|-]

Emit compiler output in UTF-8.

Utf8Output

@<file>

Read command-line settings from a response file.

CompilerResponseFile

-vbruntime[+|-|*]

+ use the default runtime, - none, * embed the runtime into the output.

VBRuntime (Default, Embed), UseReferencedVBRuntime

-vbruntime:<file>

Compile against the alternate runtime in the given file.

VBRuntime set to a path, or UseReferencedVBRuntime

Accepted but not listed by -help

Option Short Purpose MSBuild counterpart

-features:<list>

Experimental compiler feature flags.

Features

-embed[:<file>]

Embed all or the named source files in the PDB.

EmbedAllSources, EmbeddedFiles item

-analyzerconfig:<file>

An .editorconfig or global analyzer config file.

EditorConfigFiles items

-novbruntimeref

Do not reference the Visual Basic runtime.

NoVBRuntimeReference

-runtimemetadataversion:<version>

Passed by the task; this compiler prints warning BC2007 (unrecognized option; ignored).

RuntimeMetadataVersion

Observations from running the options

The results that were not simply "accepted":

Option as passed What the compiler said

-link:lib.dll (an ordinary library)

error BC31549: Cannot embed interop types from assembly 'lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' because it is missing the 'System.Runtime.InteropServices.GuidAttribute' attribute. (BC31553 follows for the type-library attributes.) The option only works on interop assemblies.

-recurse:*.vb next to x.vb

error BC30179: module 'M' and module 'M' conflict in namespace '<Default>'. The wildcard re-added the file that was already on the command line.

-win32icon:nonexistent.ico, -win32resource:nonexistent.res

error BC31509: Unable to open resource file '...': Could not find file '...'.

-win32manifest:nonexistent.manifest

error BC31192: Unable to open Win32 manifest file '...' : Could not find file '...'.

-ruleset:nonexistent.ruleset

error BC37232: Error reading ruleset file ... - File not found.

-sourcelink:nonexistent.json with -debug:portable

error BC2012: can't open '...' for writing: Could not find file '...'.; without a PDB: error BC37264: /sourcelink switch is only supported when emitting PDB.

-embed without -debug:portable

error BC37265: /embed switch is only supported when emitting a PDB.

-instrument:TestCoverage

error BC35000: Requested operation is not available because the runtime library function 'Microsoft.CodeAnalysis.Runtime.Instrumentation.CreatePayload' is not defined. The instrumented code needs a helper that coverage tools supply.

-delaysign+ without a key

warning BC40060: Delay signing was specified and requires a public key, but no public key was specified.

-publicsign+ without a key

error BC37254: Public sign was specified and requires a public key, but no public key was specified

-keyfile:nonexistent.snk

error BC36980: Error extracting public key from file 'nonexistent.snk': File not found.

-keycontainer:Box

error BC36981: Error extracting public key from container 'Box': Assembly signing not supported.

-moduleassemblyname:Shell with -target:exe

error BC2030: the /moduleassemblyname option may only be specified when building a target of type 'module'; accepted with -target:module.

-analyzer: the SDK’s NetAnalyzers DLL

accepted, with warning BC42376: An instance of analyzer ... cannot be created from ... : System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.CodeAnalysis.NetAnalyzers, ...' because the sibling analyzer assembly was not passed too (the SDK passes both, as the captured command line shows).

-vbruntime+

error BC2017: could not find library 'Microsoft.VisualBasic.dll' (it searches the SDK path, which -nosdkpath disabled); -vbruntime-, -vbruntime* and -vbruntime:<file> were accepted.

-runtimemetadataversion:v4.0.30319

warning BC2007: unrecognized option (even though the MSBuild task passes it).

-bogusoption

warning BC2007: unrecognized option '-bogusoption'; ignored

-langversion:?

prints the supported values and exits 0 (see above); -help and -? print the usage; -version prints 5.9.0-1.26505.111 (a8dd0577c5c13f43a2f57fe33b4ff11b90f6371e).

-verbose

lists every reference as Adding assembly reference '...'.

Two further results matter for scripts. The same options with a trailing + or - (-deterministic+, -deterministic-, -optimize-, -highentropyva-, -utf8output-, -delaysign-, -publicsign-, -skipanalyzers-, -parallel-, -doc-, -removeintchecks-, -optionexplicit+, -debug-) were all accepted, including forms the help text shows without a suffix. And -define:A=1,B was accepted (a symbol without a value is True), while ; as a separator belongs to the MSBuild property, not to the command-line option, as the DefineConstants experiment showed.

Running vbc by hand

Nothing prevents compiling without MSBuild; you need the reference assemblies and the right standard-library options because vbc on .NET does not find them itself. The project below is one source file, a response file with the options and a response file listing the 167 reference assemblies (made with ls $REF/*.dll | sed 's|^|-reference:|' > refs.rsp, where REF is the microsoft.netcore.app.ref folder .../ref/net10.0 of the reference pack):

Option Strict On

Public Module Hello

    Public Sub Main()
        Console.WriteLine("Hello from vbc " & Left("compiled by hand", 11))
    End Sub

End Module
# build.rsp
-nologo
-noconfig
-nostdlib
-nosdkpath
-target:exe
-out:Hello.dll
-optionstrict+
-rootnamespace:Hand
-define:_MyType="Empty"
-imports:System,Microsoft.VisualBasic
-vbruntime:<ref>/Microsoft.VisualBasic.dll
@refs.rsp
Hello.vb
dotnet /usr/share/dotnet/sdk/10.0.402/Roslyn/bincore/vbc.dll @build.rsp
vbc : warning BC2025: ignoring /noconfig option because it was specified in a response file
vbc exit code: 0

The Hello.runtimeconfig.json that dotnet needs to run a framework-dependent program (the SDK writes it for you in a project build) is three lines of JSON that name the framework Microsoft.NETCore.App version 10.0.0; with it, dotnet Hello.dll printed:

Hello from vbc compiled by

Notes from the run: -noconfig must be on the real command line (BC2025 otherwise, a warning only); without -define:_MyType="Empty" the compiler generates its default My classes and reports BC30002 for the Windows-only types they use (Type 'Global.Microsoft.VisualBasic.ApplicationServices.ApplicationBase' is not defined.), and the line is how the SDK stops that; and a response file is simply the options, one per line, expanded in place, which is how MSBuild itself invokes the compiler. Built and run: 1 program.

The Vbc task parameters

The complete interface between MSBuild and the compiler is the list of parameters that Microsoft.VisualBasic.Core.targets gives to the Vbc task in the CoreCompile target: 90 of them in this SDK. For each, the table gives the property or item that feeds it, the compiler option it becomes and whether Learn’s Vbc task page documents it. (Learn lists 53 names, and 50 of them match a parameter of the 90.) A property that is not in the "Fed by" column cannot influence the compiler through the SDK targets.

Vbc task parameter Fed by Becomes On Learn

AdditionalLibPaths

AdditionalLibPaths

/libpath

yes

AddModules

AddModules items

/addmodule

yes

AdditionalFiles

AdditionalFiles items

/additionalfile

no

AnalyzerConfigFiles

EditorConfigFiles items

/analyzerconfig

no

Analyzers

Analyzer items

/analyzer

no

BaseAddress

BaseAddress

/baseaddress

yes

ChecksumAlgorithm

ChecksumAlgorithm

/checksumalgorithm

no

CodeAnalysisRuleSet

ResolvedCodeAnalysisRuleSet (from CodeAnalysisRuleSet)

/ruleset

no

CodePage

CodePage

/codepage

yes

DebugType

DebugType

/debug:...

yes

DefineConstants

FinalDefineConstants

/define

yes

DelaySign

DelaySign

/delaysign

yes

DisableSdkPath

DisableSdkPath

/nosdkpath

no

DisabledWarnings

NoWarn

/nowarn:list

yes

DocumentationFile

DocFileItem item

/doc:file

yes

EmbedAllSources

EmbedAllSources

/embed

no

EmbeddedFiles

EmbeddedFiles items

/embed:file

no

EmitDebugInformation

DebugSymbols

/debug+ or /debug-

yes

EnvironmentVariables

VbcEnvironment

(environment of the compiler process)

no

ErrorLog

ErrorLog

/errorlog

no

ErrorReport

ErrorReport

/errorreport

yes

Features

Features

/features

no

FileAlignment

FileAlignment

/filealign

yes

GenerateDocumentation

GenerateDocumentation

/doc+ (the SDK targets feed DocFileItem instead)

yes

GeneratedFilesOutputPath

CompilerGeneratedFilesOutputPath

/generatedfilesout

no

HighEntropyVA

HighEntropyVA

/highentropyva

no

Imports

Import items

/imports

yes

Instrument

Instrument

/instrument

no

KeyContainer

KeyContainerName

/keycontainer

yes

KeyFile

KeyOriginatorFile

/keyfile

yes

LangVersion

LangVersion

/langversion

yes

LinkResources

LinkResource items

/linkresource

yes

MainEntryPoint

StartupObject

/main

yes

ModuleAssemblyName

ModuleAssemblyName

/moduleassemblyname

yes

NoConfig

always true

/noconfig

yes

NoStandardLib

NoCompilerStandardLib

/nostdlib

yes

NoVBRuntimeReference

NoVBRuntimeReference

/novbruntimeref

yes

NoWarnings

WarningLevel 0 (as _NoWarnings)

/nowarn

yes

NoWin32Manifest

NoWin32Manifest

/nowin32manifest

no

Optimize

Optimize

/optimize

yes

Deterministic

Deterministic

/deterministic

yes

PublicSign

PublicSign

/publicsign

no

OptionCompare

OptionCompare

/optioncompare

yes

OptionExplicit

OptionExplicit

/optionexplicit

yes

OptionInfer

OptionInfer

/optioninfer

yes

OptionStrict

OptionStrict

/optionstrict

yes

OptionStrictType

OptionStrictType

/optionstrict:custom

yes

OutputAssembly

IntermediateAssembly item

/out

yes

OutputRefAssembly

IntermediateRefAssembly item

/refout

no

PdbFile

PdbFile

(PDB path; used for winmdobj)

no

Platform

PlatformTarget

/platform

yes

Prefer32Bit

Prefer32Bit

/platform:anycpu32bitpreferred

no

PreferredUILang

PreferredUILang

/preferreduilang

no

ProjectName

MSBuildProjectName

(diagnostics only)

no

ProvideCommandLineArgs

ProvideCommandLineArgs

(return the arguments as an item)

no

References

ReferencePathWithRefAssemblies items

/reference

yes

RefOnly

ProduceOnlyReferenceAssembly

/refonly

no

RemoveIntegerChecks

RemoveIntegerChecks

/removeintchecks

yes

ReportAnalyzer

ReportAnalyzer

/reportanalyzer

no

ReportIVTs

ReportIVTs

/reportivts

no

Resources

EmbeddedResource items (and compiled license files)

/resource

yes

ResponseFiles

CompilerResponseFile

@file

yes

RootNamespace

RootNamespace

/rootnamespace

yes

RuntimeMetadataVersion

RuntimeMetadataVersion

/runtimemetadataversion (ignored by vbc)

no

SdkPath

FrameworkPathOverride

/sdkpath

yes

SharedCompilationId

SharedCompilationId

(build server pipe name)

no

SkipAnalyzers

_SkipAnalyzers (from RunAnalyzers)

/skipanalyzers

no

SkipCompilerExecution

SkipCompilerExecution

(do not run the compiler)

no

Sources

Compile items

(source file names)

yes

SubsystemVersion

SubsystemVersion

/subsystemversion

no

TargetCompactFramework

TargetCompactFramework

/netcf

yes

TargetType

OutputType

/target

yes

TargetFramework

TargetFramework

(used for task logic and symbols)

no

ToolExe

VbcToolExe

(which executable to run)

no

ToolPath

VbcToolPath

(folder of the executable)

yes

TreatWarningsAsErrors

TreatWarningsAsErrors

/warnaserror

yes

UseHostCompilerIfAvailable

UseHostCompilerIfAvailable

(legacy in-IDE compiler)

yes

UseSharedCompilation

UseSharedCompilation

(build server or not)

no

Utf8Output

Utf8Output

/utf8output

yes

VBRuntimePath

VBRuntimePath

(path of the runtime)

no

Verbosity

VbcVerbosity

/quiet or /verbose

yes

VsSessionGuid

VsSessionGuid

(IDE session id)

no

WarningsAsErrors

WarningsAsErrors

/warnaserror+:list

yes

WarningsNotAsErrors

WarningsNotAsErrors

/warnaserror-:list

yes

Win32Icon

ApplicationIcon

/win32icon

yes

Win32Manifest

Win32Manifest

/win32manifest

no

Win32Resource

Win32Resource

/win32resource

no

VBRuntime

VBRuntime

/vbruntime

no

PathMap

PathMap

/pathmap

no

SourceLink

SourceLink

/sourcelink

no

The 40 parameters Learn does not document include most of the modern ones: analyzers and analyzer configs, ErrorLog, Features, PathMap, PublicSign, SourceLink, EmbedAllSources, RefOnly, SkipAnalyzers, Instrument, ChecksumAlgorithm and HighEntropyVA. Reading the targets file is the reliable way to find them. Source: Vbc task, MSBuild tasks

Visual Studio project properties

Visual Studio is a front end for the same project file: every setting it shows is an MSBuild property, and saving the page edits the .vbproj. For an SDK-style project, double-clicking the project in Solution Explorer (or the Edit Project File command) opens the XML directly, which is the quickest way to see what a page really stores. No IDE was available for the tests behind this page, so the table below takes the page and setting names from the Learn "Project Designer" pages for Visual Basic and the property names from the verified sections above; check the page layout against your Visual Studio version (the two Learn pages state that they apply to Visual Studio 2017, and the project designer has been redone since).

Page and setting MSBuild property vbc option Notes

Application: Assembly name

AssemblyName

-out

Changing it also changes the output name.

Application: Root namespace

RootNamespace

-rootnamespace

Empty means no prefix.

Application: Target framework

TargetFramework, TargetFrameworks

(reference pack, symbols)

Multi-targeting is edited in the XML.

Application: Application type

OutputType

-target

Console Application, Windows Application, Class Library.

Application: Startup object

StartupObject

-main

Sub Main or a form.

Application: Icon

ApplicationIcon

-win32icon

Application: Enable application framework

MyType

(_MyType symbol)

WindowsForms with the framework, WindowsFormsWithCustomSubMain without.

Application: Assembly Information

Version, AssemblyVersion, FileVersion, Company, Product, Description, Copyright

(AssemblyInfo attributes)

Written to the project file in SDK-style projects.

Compile: Option explicit / strict / compare / infer

OptionExplicit, OptionStrict, OptionCompare, OptionInfer

-optionexplicit, -optionstrict, -optioncompare, -optioninfer

The page also offers per-warning settings (implicit conversion, late binding, implicit type) that map to custom Option Strict.

Compile: Disable all warnings / Treat all warnings as errors

WarningLevel or NoWarn, TreatWarningsAsErrors

-nowarn, -warnaserror

The per-warning table maps to NoWarn, WarningsAsErrors, WarningsNotAsErrors.

Compile: Generate XML documentation file

GenerateDocumentationFile

-doc

Compile: Target CPU, Prefer 32-bit

PlatformTarget, Prefer32Bit

-platform

Compile: Advanced compile options

Optimize, DebugType, DefineDebug, DefineTrace, DefineConstants, RemoveIntegerChecks, BaseAddress

-optimize, -debug, -define, -removeintchecks, -baseaddress

The names in the dialog (for example "Custom constants") are not the property names.

Compile: Build output path

OutputPath

(not a compiler option)

References, Resources, Settings

Reference, ProjectReference, PackageReference items; EmbeddedResource; None with Generator

-reference, -resource

Settings files generate My.Settings code; see The My Namespace.

New projects start from the defaults of Tools > Options > Projects and Solutions > VB Defaults, which is why a project created in Visual Studio can have OptionStrict On while one created with dotnet new has Off: the IDE writes the option into the new project, and the SDK default is only the fallback. The debugger, Edit and Continue and Hot Reload are on Debugging and Diagnostics.

Legacy (non-SDK) project files

Until .NET Core, every Visual Basic project used a verbose format that listed everything by hand. You meet it in .NET Framework solutions, in older repositories and in the books that predate the SDK. The sample below is a typical project of that kind for a console application (legacy format, shown for recognition; not built here).

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
          Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{00000000-0000-0000-0000-000000000000}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <StartupObject>Shelf.Cli.Program</StartupObject>
    <RootNamespace>Shelf.Cli</RootNamespace>
    <AssemblyName>Shelf.Cli</AssemblyName>
    <FileAlignment>512</FileAlignment>
    <MyType>Console</MyType>
    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
    <OptionExplicit>On</OptionExplicit>
    <OptionCompare>Binary</OptionCompare>
    <OptionStrict>Off</OptionStrict>
    <OptionInfer>On</OptionInfer>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <DefineDebug>true</DefineDebug>
    <DefineTrace>true</DefineTrace>
    <OutputPath>bin\Debug\</OutputPath>
    <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml.Linq" />
  </ItemGroup>
  <ItemGroup>
    <Import Include="Microsoft.VisualBasic" />
    <Import Include="System" />
    <Import Include="System.Collections.Generic" />
    <Import Include="System.Linq" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.vb" />
    <Compile Include="My Project\AssemblyInfo.vb" />
    <Compile Include="My Project\Application.Designer.vb">
      <AutoGen>True</AutoGen>
      <DependentUpon>Application.myapp</DependentUpon>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
    <None Include="My Project\Application.myapp">
      <Generator>MyApplicationCodeGenerator</Generator>
      <LastGenOutput>Application.Designer.vb</LastGenOutput>
    </None>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
</Project>

What the SDK-style project made redundant:

Legacy construct SDK-style replacement

ToolsVersion, the xmlns, the Microsoft.Common.props and Microsoft.VisualBasic.targets imports

<Project Sdk="Microsoft.NET.Sdk">

ProjectGuid, default Configuration and Platform

Not needed (the solution file still carries project ids)

TargetFrameworkVersion v4.8

TargetFramework net48 or net481; the same project file can then move to net10.0

One PropertyGroup per configuration with DefineDebug, DefineTrace, OutputPath, NoWarn

The SDK defaults (the same NoWarn list as above) and Condition="'$(Configuration)' == 'Release'" groups only for real differences

A Compile Include for every file

The **/*.vb glob (NETSDK1022 if you keep the list)

Reference Include="System" and HintPath into a packages folder with packages.config

Implicit framework references and PackageReference

Import Include items for the default namespaces

The SDK’s implicit imports (keep only your own)

My Project\AssemblyInfo.vb

Generated from Version, Company and so on; keeping the old file with GenerateAssemblyInfo true produces duplicate-attribute errors (BC30663), so either delete the file or set GenerateAssemblyInfo to false

My Project\Application.myapp and Application.Designer.vb

Still used by Windows Forms projects that use the application framework; Console and library projects need only MyType

Settings.settings, Resources.resx

Unchanged in structure; they stay None / EmbeddedResource items

Almost every property name is the same in both formats (OutputType, RootNamespace, OptionStrict, MyType, DefineConstants), which is why a conversion is mostly deletion. A reasonable order is: create an SDK-style project that targets the same .NET Framework version (net48, net481: it compiles anywhere with the reference assemblies package, as the multi-targeting example showed), delete the lists the SDK now provides, replace packages.config with PackageReference, fix the AssemblyInfo duplicates, get the build green on the old framework, and only then change the target to net10.0 and deal with the APIs that moved. Microsoft’s Upgrade Assistant and the app-modernization tooling can automate part of this; their current capabilities were not tested for this page. Web Forms and ASP.NET MVC projects are .NET Framework projects, normally in the legacy format, as described in Web, HTTP and Services. The strategy side is on Migrating Legacy Visual Basic.

Pitfalls checklist

What you see Why Fix

BC42016 and other Option Strict warnings suddenly appear everywhere

NoWarn was assigned without $(NoWarn), removing the SDK’s default list

<NoWarn>$(NoWarn);BC42024</NoWarn>, or set OptionStrict On

BC31030 "Conditional compilation constant …​ is not valid"

Semicolon (or an empty first entry) in DefineConstants

Comma separated: $(DefineConstants),A=1,B=2

DEBUG is still defined after DefineDebug false

ImplicitConfigurationDefine re-adds the upper-cased configuration name

Use another configuration name or DisableImplicitConfigurationDefines

MSB4030 for OptionStrict Custom

The task parameter is a Boolean

Off already gives /optionstrict:custom

NETSDK1022 duplicate Compile items

A legacy project still lists the files

Delete the items, or EnableDefaultCompileItems false

BC30738 "'Sub Main' is declared more than once"

Two entry points

StartupObject with the root namespace prefix

Namespaces come out as Shelf.Cli.Shelf.Cli

Namespace Shelf.Cli written inside a project whose root namespace is already Shelf.Cli

Write the namespace without the root namespace

BC2014 for LangVersion 17, 17.0, preview

VB has no such values

17.13, latest or default

ImplicitUsings, Nullable, Using do nothing

C# features

Import items and file-level Imports

BC30002 for StringBuilder, List, Regex

Missing import (or DisableImplicitNamespaceImports)

Imports in the file or an Import item

BC30451 for Left, Mid, Format

Microsoft.VisualBasic not imported

Imports Microsoft.VisualBasic, or do not disable the implicit imports

BC30663 duplicate attribute

Hand-written AssemblyInfo.vb and a generated one

Delete one, or GenerateAssemblyInfo false

BC2007 "unrecognized option"

An option the compiler does not know (RuntimeMetadataVersion, or a typo)

Remove it; warnings of this kind do not fail the build

-p:StartupObject=... seems ignored by dotnet run

An incremental build kept the old output

dotnet build --no-incremental first

A project value is ignored

A -p:Name=Value global property beats the project file, Directory.Build.props and the SDK

Remove the command-line property, or set it conditionally

A net481 build runs on Linux nowhere

It can be compiled anywhere, run only on Windows

Build it in CI on any OS, test it on Windows

MyType Console fails with BC30002

On .NET 10 the Console My classes are not available

Leave MyType empty for console programs and libraries

Differences from C# and from .NET Framework

Topic Visual Basic C#

Project templates

Fewer: console, classlib, test, WinForms, WPF; none for web, worker or MAUI

All of them

Namespace prefix

RootNamespace is prepended to every Namespace block

No such property; the Namespace is as written

Language strictness

OptionStrict, OptionExplicit, OptionInfer, OptionCompare are project settings; SDK default OptionStrict is Off

Equivalent behaviour is fixed (or Nullable, CheckForOverflowUnderflow as settings)

Overflow

RemoveIntegerChecks removes checks that are on by default

CheckForOverflowUnderflow adds checks that are off by default

Symbols

DefineConstants is comma separated; DEBUG, TRACE and the configuration name; _MyType, VBC_VER

Semicolon separated; DEBUG, TRACE

Global imports

Import items; no global using, no ImplicitUsings

global using, ImplicitUsings, Using items

Language versions

9 to 16.9, 17.13, default, latest; no preview; no new syntax since 15.5 (apart from where comments may go)

Numbers up to the current one, preview, latest, default, latestMajor

Runtime library

Microsoft.VisualBasic with My, controlled by MyType and VBRuntime

None

Compared with .NET Framework. An SDK-style project can target net48 or net481 and keep MyType Windows / WindowsForms, System.Data in the default imports, /subsystemversion:6.00 and the Framework’s own Microsoft.VisualBasic.dll; a project on net10.0 gets _MyType="Empty" until the template sets another value, eight default imports and the reference pack’s runtime.

Where the books and the older documentation are outdated

The books that predate the SDK-style format describe Visual Basic through a .NET Framework console template and a Visual Studio of that time; on current tooling the same programs are created with dotnet new console -lang VB or File > New in Visual Studio 2026 and build against .NET 10 (see Getting Started). Beyond the books, a few official pages lag behind the product, and a reader following them gets a wrong picture:

  • The Learn pages -langversion and Select the Visual Basic language version list LangVersion values only up to 16.9 (the compiler also accepts 17.13), and the first says the option can be set only on the command line, which is not true for a project.

  • The Learn Vbc task page lists 53 parameters, 50 of them still current; the targets pass 90.

  • The Learn MSBuild properties for Microsoft.NET.Sdk page does not mention the Visual Basic-specific properties (OptionStrict, OptionExplicit, OptionInfer, OptionCompare, RemoveIntegerChecks, MyType, VBRuntime, RootNamespace, StartupObject and DefineConstants each had zero matches in the page), so this page’s tables come from the SDK files themselves.

  • The Learn compiler-option pages do not cover everything vbc -help prints; the options -analyzer, -additionalfile, -checksumalgorithm, -errorlog, -ruleset, -reportanalyzer, -skipanalyzers, -instrument, -sourcelink, -publicsign, -generatedfilesout, -parallel, -preferreduilang, -nosdkpath, -reportivts and -version have no page named after them.

  • The Learn Compile Page and Application Page of the Project Designer say they apply to Visual Studio 2017.

  • The default OptionStrict is Off in the SDK although Learn and every style guide recommend On; there is no project template that changes it.

Summary

  • A Visual Basic project is an SDK-style .vbproj; the SDK adds the sources (glob), the references, eight global imports, the option defaults and a NoWarn list.

  • Properties become vbc options through the Vbc task (90 parameters). dotnet msbuild -getTargetResult:CoreCompile with ProvideCommandLineArgs shows the result without compiling.

  • OptionStrict Off is passed as /optionstrict:custom and relies on the default NoWarn; write $(NoWarn);..., or better set OptionStrict On in Directory.Build.props.

  • DefineConstants is comma separated; DefineDebug false does not remove DEBUG; Import items replace ImplicitUsings; Nullable and Using are inert.

  • LangVersion accepts 9 to 16.9, 17.13, default and latest; the syntax gates are those of the language history, and no syntax has been added since 15.5.

  • MyType, VBRuntime and UseReferencedVBRuntime choose the My classes and the runtime; they differ for net10.0, net10.0-windows, net481 and netstandard2.0.

  • Share settings with Directory.Build.props, Directory.Packages.props and global.json; a command-line -p: property wins over all of them.

  • dotnet new -lang VB, sln, add, build, run, test, pack, publish and format are the day-to-day commands; dotnet msbuild -pp shows the expanded project.