├── Solution ├── Exercise04 │ ├── PBI-Tool │ │ ├── Query.dat │ │ ├── Servers.dat │ │ ├── PBI-Tool.csproj │ │ ├── .vscode │ │ │ ├── launch.json │ │ │ └── tasks.json │ │ └── Program.cs │ └── DAXFormatter.pbitool.json ├── Exercise03 │ └── PBI-Tool │ │ ├── QueryResuts.csv │ │ ├── PBI-Tool.csproj │ │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ │ └── Program.cs ├── Exercise01 │ └── PBI-Tool │ │ ├── PBI-Tool.csproj │ │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ │ └── Program.cs └── Exercise02 │ └── PBI-Tool │ ├── PBI-Tool.csproj │ ├── .vscode │ ├── launch.json │ └── tasks.json │ └── Program.cs ├── Tutorial.docx ├── Tutorial.pdf ├── TOM_Figures.pptx ├── Report Templates.pptx ├── PBIX └── Wingtip Sales Model.pbix ├── Demos ├── Learning-TOM │ ├── Learning-TOM │ │ ├── DAX │ │ │ ├── QueryGetSalesByState.dax │ │ │ ├── CalculatedTable-Calendar.dax │ │ │ ├── CalculatedColumn-AgeGroup.dax │ │ │ ├── CalculatedColumn-SalesRegionSort.dax │ │ │ └── CalculatedColumn-SalesRegion.dax │ │ ├── M │ │ │ ├── SalesQuery.m │ │ │ ├── ProductQuery.m │ │ │ └── CustomersQuery.m │ │ ├── Program.cs │ │ ├── Learning-TOM.csproj │ │ ├── Properties │ │ │ ├── Resources.resx │ │ │ └── Resources.Designer.cs │ │ └── DatasetManager.cs │ └── Learning-TOM.sln ├── DaxFormatterExternalTool │ ├── DaxFormatterExternalTool.csproj │ ├── Program.cs │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ └── DaxFormatter.cs └── DAXFormatter.pbitool.json ├── README.md └── StarterFiles ├── Exercise01-Starter-Program.cs ├── Exercise01-Final-Program.cs ├── Exercise02-Program.cs ├── Exercise03-Program.cs ├── pbitools.pbitool.json └── Exercise04-Program.cs /Solution/Exercise04/PBI-Tool/Query.dat: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Solution/Exercise04/PBI-Tool/Servers.dat: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Tutorial.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/Tutorial.docx -------------------------------------------------------------------------------- /Tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/Tutorial.pdf -------------------------------------------------------------------------------- /TOM_Figures.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/TOM_Figures.pptx -------------------------------------------------------------------------------- /Report Templates.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/Report Templates.pptx -------------------------------------------------------------------------------- /PBIX/Wingtip Sales Model.pbix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/PBIX/Wingtip Sales Model.pbix -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/DAX/QueryGetSalesByState.dax: -------------------------------------------------------------------------------- 1 | EVALUATE 2 | SUMMARIZECOLUMNS( 3 | Customers[State], 4 | "Sales Revenue", SUM(Sales[SalesAmount]), 5 | "Units Sold", SUM(Sales[Quantity]) 6 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tabular-Object-Model-Tutorial 2 | This repo provides a tutorial and sample code programming datasets using the Tabular Object Model with Power BI Desktop and the Power BI Service via the XMLA endpoint. 3 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/DAX/CalculatedTable-Calendar.dax: -------------------------------------------------------------------------------- 1 | Var CalenderStart = Date(Year(Min(Sales[InvoiceDate])) , 1, 1) 2 | Var CalendarEnd = Date(Year(MAX(Sales[InvoiceDate])), 12, 31) 3 | Return 4 | CALENDAR(CalenderStart, CalendarEnd) 5 | -------------------------------------------------------------------------------- /Solution/Exercise03/PBI-Tool/QueryResuts.csv: -------------------------------------------------------------------------------- 1 | Customers[State],[Sales Revenue],[Units Sold] 2 | CA,5255912.41,623260 3 | OR,1849876.53,214463 4 | WA,1785527.07,182481 5 | NM,843053.8,125201 6 | CO,1089855.17,169969 7 | AZ,1515934.54,223647 8 | UT,393728.68,59104 9 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/DAX/CalculatedColumn-AgeGroup.dax: -------------------------------------------------------------------------------- 1 | SWITCH ( 2 | TRUE(), 3 | [Age] >= 65, "65 and over", 4 | [Age] >= 50, "50 to 64", 5 | [Age] >= 40, "40 to 49", 6 | [Age] >= 30, "30 to 39", 7 | [Age] >= 18, "18 to 29", 8 | [Age] < 18, "Under 18" 9 | ) -------------------------------------------------------------------------------- /Solution/Exercise01/PBI-Tool/PBI-Tool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | PBI_Tool 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Solution/Exercise02/PBI-Tool/PBI-Tool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | PBI_Tool 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/DAX/CalculatedColumn-SalesRegionSort.dax: -------------------------------------------------------------------------------- 1 | SWITCH( 2 | TRUE(), 3 | [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"}, 4 | 1, 5 | [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"}, 6 | 2, 7 | [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"}, 8 | 3 9 | ) -------------------------------------------------------------------------------- /Demos/DaxFormatterExternalTool/DaxFormatterExternalTool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/DAX/CalculatedColumn-SalesRegion.dax: -------------------------------------------------------------------------------- 1 | SWITCH( 2 | TRUE(), 3 | [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"}, 4 | "Western Region", 5 | [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"}, 6 | "Central Region", 7 | [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"}, 8 | "Eastern Region" 9 | ) -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/M/SalesQuery.m: -------------------------------------------------------------------------------- 1 | let 2 | Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"), 3 | dbo_InvoiceDetails = Source{[Schema="dbo",Item="InvoiceDetails"]}[Data], 4 | ExpandedInvoices = Table.ExpandRecordColumn(dbo_InvoiceDetails, "Invoices", {"InvoiceDate", "CustomerId"}, {"InvoiceDate", "CustomerId"}), 5 | RemovedColumns = Table.RemoveColumns(ExpandedInvoices,{"Products"}), 6 | ChangedType = Table.TransformColumnTypes(RemovedColumns,{{"InvoiceDate", type date}, {"SalesAmount", Currency.Type}}) 7 | in 8 | ChangedType -------------------------------------------------------------------------------- /Solution/Exercise03/PBI-Tool/PBI-Tool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | PBI_Tool 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Solution/Exercise04/PBI-Tool/PBI-Tool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | PBI_Tool 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demos/DaxFormatterExternalTool/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AnalysisServices.Tabular; 2 | 3 | namespace DaxFormatterExternalTool { 4 | 5 | class Program { 6 | 7 | static void Main(string[] args) { 8 | 9 | string connectString = args.Length >= 1 ? args[0] : "localhost:53610"; 10 | 11 | Server server = new Server(); 12 | server.Connect(connectString); 13 | 14 | Model model = server.Databases[0].Model; 15 | 16 | DaxFormatter.FormatDaxForModel(model); 17 | 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StarterFiles/Exercise01-Starter-Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | 4 | class Program { 5 | 6 | const string connectString = "localhost:50000"; // update with port number on your machine 7 | 8 | static void Main(string[] args) { 9 | 10 | Server server = new Server(); 11 | server.Connect(connectString); 12 | 13 | Model model = server.Databases[0].Model; 14 | 15 | foreach(Table table in model.Tables) { 16 | Console.WriteLine($"Table : {table.Name}"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | 4 | namespace Learning_TOM { 5 | 6 | class Program { 7 | 8 | static void Main() { 9 | 10 | string DatabaseName = "Demo Dataset 1"; 11 | 12 | DatasetManager.ConnectToPowerBIAsUser(); 13 | Database database = DatasetManager.CreateDatabase(DatabaseName); 14 | DatasetManager.CreateWingtipSalesModel(database); 15 | 16 | //string newDatabaseName = "My Cloned Dataset Copy"; 17 | //DatasetManager.CopyDatabase(DatabaseName, newDatabaseName); 18 | //database = DatasetManager.CreateDatabase(newDatabaseName); 19 | 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/M/ProductQuery.m: -------------------------------------------------------------------------------- 1 | let 2 | Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"), 3 | dbo_Products = Source{[Schema="dbo",Item="Products"]}[Data], 4 | RemovedOtherColumns = Table.SelectColumns(dbo_Products,{"ProductId", "Title", "Description", "ProductCategory", "ProductImageUrl"}), 5 | RenamedColumns = Table.RenameColumns(RemovedOtherColumns,{{"Title", "Product"}}), 6 | SplitColumnByDelimiter = Table.SplitColumn(RenamedColumns, "ProductCategory", Splitter.SplitTextByDelimiter(" > ", QuoteStyle.Csv), {"ProductCategory.1", "ProductCategory.2"}), 7 | ChangedType = Table.TransformColumnTypes(SplitColumnByDelimiter,{{"ProductCategory.1", type text}, {"ProductCategory.2", type text}}), 8 | RenamedColumns1 = Table.RenameColumns(ChangedType,{{"ProductCategory.1", "Category"}, {"ProductCategory.2", "Subcategory"}}) 9 | in 10 | RenamedColumns1 -------------------------------------------------------------------------------- /Demos/DaxFormatterExternalTool/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net5.0/DaxFormatterExternalTool.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "externalTerminal", 16 | "stopAtEntry": false 17 | }, 18 | { 19 | "name": ".NET Core Attach", 20 | "type": "coreclr", 21 | "request": "attach", 22 | "processId": "${command:pickProcess}" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /Solution/Exercise01/PBI-Tool/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false, 17 | "logging": { "moduleLoad": false } 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Solution/Exercise02/PBI-Tool/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false, 17 | "logging": { "moduleLoad": false } 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Solution/Exercise03/PBI-Tool/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false, 17 | "logging": { "moduleLoad": false } 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Solution/Exercise04/PBI-Tool/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "externalTerminal", 16 | "stopAtEntry": false, 17 | "logging": { "moduleLoad": false } 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/Learning-TOM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Learning_TOM 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | True 17 | True 18 | Resources.resx 19 | 20 | 21 | 22 | 23 | 24 | ResXFileCodeGenerator 25 | Resources.Designer.cs 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Solution/Exercise01/PBI-Tool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | 4 | class Program { 5 | 6 | const string connectString = "localhost:57308"; // update with port number on your machine 7 | 8 | static void Main(string[] args) { 9 | 10 | Server server = new Server(); 11 | server.Connect(connectString); 12 | 13 | Model model = server.Databases[0].Model; 14 | 15 | // foreach (Table table in model.Tables) { 16 | // Console.WriteLine($"Table : {table.Name}"); 17 | // } 18 | 19 | Table table = model.Tables["Sales"]; 20 | 21 | if (table.Measures.ContainsName("VS Code Measure")) { 22 | Measure measure = table.Measures["VS Code Measure"]; 23 | measure.Expression = "\"Hello Again World\""; 24 | } else { 25 | Measure measure = new Measure() { 26 | Name = "VS Code Measure", 27 | Expression = "\"Hello World\"" 28 | }; 29 | table.Measures.Add(measure); 30 | } 31 | 32 | model.SaveChanges(); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /StarterFiles/Exercise01-Final-Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | 4 | class Program { 5 | 6 | const string connectString = "localhost:50000"; // update with port number on your machine 7 | 8 | static void Main(string[] args) { 9 | 10 | Server server = new Server(); 11 | server.Connect(connectString); 12 | 13 | Model model = server.Databases[0].Model; 14 | 15 | // foreach(Table table in model.Tables) { 16 | // Console.WriteLine($"Table : {table.Name}"); 17 | // } 18 | 19 | Table table = model.Tables["Sales"]; 20 | 21 | if (table.Measures.ContainsName("VS Code Measure")) { 22 | Measure measure = table.Measures["VS Code Measure"]; 23 | measure.Expression = "\"Hello Again World\""; 24 | } 25 | else { 26 | Measure measure = new Measure() { 27 | Name = "VS Code Measure", 28 | Expression = "\"Hello World\"" 29 | }; 30 | table.Measures.Add(measure); 31 | } 32 | 33 | model.SaveChanges(); 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30626.31 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Learning-TOM", "Learning-TOM\Learning-TOM.csproj", "{281FE7D1-97C1-43C7-A9CF-B4121E919AFE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {67A6AAFB-A3DF-42DE-90DB-89D54C7B169C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Solution/Exercise01/PBI-Tool/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/PBI-Tool.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/PBI-Tool.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/PBI-Tool.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Solution/Exercise02/PBI-Tool/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/PBI-Tool.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/PBI-Tool.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/PBI-Tool.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Solution/Exercise03/PBI-Tool/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/PBI-Tool.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/PBI-Tool.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/PBI-Tool.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Solution/Exercise04/PBI-Tool/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/PBI-Tool.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/PBI-Tool.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/PBI-Tool.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Demos/DaxFormatterExternalTool/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/DaxFormatterExternalTool.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/DaxFormatterExternalTool.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/DaxFormatterExternalTool.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/M/CustomersQuery.m: -------------------------------------------------------------------------------- 1 | let 2 | Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"), 3 | dbo_Customers = Source{[Schema="dbo",Item="Customers"]}[Data], 4 | RemovedOtherColumns = Table.SelectColumns(dbo_Customers,{"CustomerId", "FirstName", "LastName", "City", "State", "Zipcode", "Gender", "BirthDate", "FirstPurchaseDate", "LastPurchaseDate"}), 5 | MergedColumns = Table.CombineColumns(RemovedOtherColumns,{"FirstName", "LastName"},Combiner.CombineTextByDelimiter(" ", QuoteStyle.None),"Customer"), 6 | ReplacedFemaleValues = Table.ReplaceValue(MergedColumns,"F","Female",Replacer.ReplaceValue,{"Gender"}), 7 | ReplacedMaleValues = Table.ReplaceValue(ReplacedFemaleValues,"M","Male",Replacer.ReplaceValue,{"Gender"}), 8 | ChangedType = Table.TransformColumnTypes(ReplacedMaleValues,{{"FirstPurchaseDate", type date}, {"LastPurchaseDate", type date}, {"BirthDate", type date}}), 9 | AddedConditionalColumn = Table.AddColumn(ChangedType, "Customer Type", each if [FirstPurchaseDate] = [LastPurchaseDate] then "One-time Customer" else "Repeat Customer"), 10 | RemovedColumns = Table.RemoveColumns(AddedConditionalColumn,{"FirstPurchaseDate", "LastPurchaseDate"}), 11 | ChangedType1 = Table.TransformColumnTypes(RemovedColumns,{{"Customer Type", type text}}), 12 | RenamedColumns = Table.RenameColumns(ChangedType1,{{"City", "City Name"}}), 13 | AddedCustom = Table.AddColumn(RenamedColumns, "City", each [City Name] & ", " + [State]), 14 | ChangedType2 = Table.TransformColumnTypes(AddedCustom,{{"City", type text}}) 15 | in 16 | ChangedType2 -------------------------------------------------------------------------------- /Demos/DAXFormatter.pbitool.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "name": "DAX Formatter", 4 | "description": "Auto Format DAX", 5 | "path": "C:\\DevCamp\\Tom-Tutorial\\PBI-Tool\\bin\\Debug\\net5.0\\PBI-Tool.exe", 6 | "arguments": "%server%", 7 | "iconData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPsAAAD6CAYAAABnLjEDAAAACXBIWXMAAAsSAAALEgHS3X78AAADp0lEQVR4nO3WsXUaQRRA0VkfCnAF1OCQzKgCWnMrVICUEaoGKqCD9RHSsWNLsr3LuzdR+ufPPjTTPM8DuH9f3DE0iB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0iNi/HvBx2exfOn9gez48fWVjkm7tuj+fnBcxxs3n7e/rPc7A+0wcnLnxzT2OMxfyoecZDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghYvN2zAcXzj9W+OauC5jhl2me54WMAvxNnvEQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iNi46M9zOey+jTG+3st5eLU9nh/fs4rLYbdfygpfziD2z/VjjPH9ng7EzfTONZwWtL7JMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4R0zzP7hoC/GeHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ8Rmjce8HHb7BYyxBtft8fxcXwKvVhn7GOO0gBnW4GmM4YeRG894iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RGxWesyHBcywBtf6AvhtmufZOiDAMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4FY4yf5wcsaJ/89DEAAAAASUVORK5CYII=" 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Solution/Exercise04/DAXFormatter.pbitool.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "name": "DAX Formatter", 4 | "description": "Auto Format DAX", 5 | "path": "C:\\DevCamp\\Tom-Tutorial\\PBI-Tool\\bin\\Debug\\net5.0\\PBI-Tool.exe", 6 | "arguments": "%server%", 7 | "iconData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPsAAAD6CAYAAABnLjEDAAAACXBIWXMAAAsSAAALEgHS3X78AAADp0lEQVR4nO3WsXUaQRRA0VkfCnAF1OCQzKgCWnMrVICUEaoGKqCD9RHSsWNLsr3LuzdR+ufPPjTTPM8DuH9f3DE0iB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0iNi/HvBx2exfOn9gez48fWVjkm7tuj+fnBcxxs3n7e/rPc7A+0wcnLnxzT2OMxfyoecZDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghYvN2zAcXzj9W+OauC5jhl2me54WMAvxNnvEQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iNi46M9zOey+jTG+3st5eLU9nh/fs4rLYbdfygpfziD2z/VjjPH9ng7EzfTONZwWtL7JMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4R0zzP7hoC/GeHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ8Rmjce8HHb7BYyxBtft8fxcXwKvVhn7GOO0gBnW4GmM4YeRG894iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RGxWesyHBcywBtf6AvhtmufZOiDAMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4FY4yf5wcsaJ/89DEAAAAASUVORK5CYII=" 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Solution/Exercise02/PBI-Tool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | 4 | class Program { 5 | 6 | const string connectString = "localhost:57308"; // update for port number on your machine 7 | 8 | static void Main(string[] args) { 9 | 10 | Server server = new Server(); 11 | server.Connect(connectString); 12 | Model model = server.Databases[0].Model; 13 | 14 | foreach (Table table in model.Tables) { 15 | foreach (Column column in table.Columns) { 16 | // determine if column is visible and numeric 17 | if ((column.IsHidden == false) & 18 | (column.DataType == DataType.Int64 || 19 | column.DataType == DataType.Decimal || 20 | column.DataType == DataType.Double)) { 21 | 22 | // add automeasure for this column new measure 23 | string measureName = $"Sum of {column.Name} ({table.Name})"; 24 | string expression = $"SUM('{table.Name}'[{column.Name}])"; 25 | string displayFolder = "Auto Measures"; 26 | 27 | Measure measure = new Measure() { 28 | Name = measureName, 29 | Expression = expression, 30 | DisplayFolder = displayFolder, 31 | Description = displayFolder 32 | }; 33 | 34 | measure.Annotations.Add(new Annotation() { Value = "This is an Auto Measure" }); 35 | 36 | if (!table.Measures.ContainsName(measureName)) { 37 | table.Measures.Add(measure); 38 | } else { 39 | table.Measures[measureName].Expression = expression; 40 | table.Measures[measureName].DisplayFolder = displayFolder; 41 | } 42 | } 43 | } 44 | } 45 | // save changes back to model in Power BI Desktop 46 | model.SaveChanges(); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /StarterFiles/Exercise02-Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | 4 | class Program { 5 | 6 | const string connectString = "localhost:50000"; // update for port number on your machine 7 | 8 | static void Main(string[] args) { 9 | 10 | Server server = new Server(); 11 | server.Connect(connectString); 12 | Model model = server.Databases[0].Model; 13 | 14 | foreach (Table table in model.Tables) { 15 | foreach (Column column in table.Columns) { 16 | // determine if column is visible and numeric 17 | if ((column.IsHidden == false) & 18 | (column.DataType == DataType.Int64 || 19 | column.DataType == DataType.Decimal || 20 | column.DataType == DataType.Double)) { 21 | 22 | // add automeasure for this column new measure 23 | string measureName = $"Sum of {column.Name} ({table.Name})"; 24 | string expression = $"SUM('{table.Name}'[{column.Name}])"; 25 | string displayFolder = "Auto Measures"; 26 | 27 | Measure measure = new Measure() { 28 | Name = measureName, 29 | Expression = expression, 30 | DisplayFolder = displayFolder, 31 | Description = displayFolder 32 | }; 33 | 34 | measure.Annotations.Add(new Annotation() { Value = "This is an Auto Measure" }); 35 | 36 | if (!table.Measures.ContainsName(measureName)) { 37 | table.Measures.Add(measure); 38 | } 39 | else { 40 | table.Measures[measureName].Expression = expression; 41 | table.Measures[measureName].DisplayFolder = displayFolder; 42 | } 43 | } 44 | } 45 | } 46 | // save changes back to model in Power BI Desktop 47 | model.SaveChanges(); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /Solution/Exercise03/PBI-Tool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TOM = Microsoft.AnalysisServices.Tabular; 3 | using Microsoft.AnalysisServices.AdomdClient; 4 | using System.Diagnostics; 5 | using System.IO; 6 | 7 | class Program { 8 | 9 | const string connectString = "Data Source=localhost:57308"; // Update port number on your machine 10 | 11 | static void Main(string[] args) { 12 | 13 | // Exercise 3 - Part 1 14 | // ExecuteDaxQuery(); 15 | 16 | // Exercise 3 - Part 2 17 | AddSalesRegionMeasures(); 18 | } 19 | 20 | static void ExecuteDaxQuery() { 21 | 22 | // DAX query to be submitted totabuar database engine 23 | String query = @" 24 | EVALUATE 25 | SUMMARIZECOLUMNS( 26 | //GROUP BY 27 | Customers[State], 28 | 29 | //FILTER BY 30 | TREATAS( {""Western Region""} , 'Customers'[Sales Region] ) , 31 | 32 | // MEASURES 33 | ""Sales Revenue"" , SUM(Sales[SalesAmount]) , 34 | ""Units Sold"" , SUM(Sales[Quantity]) 35 | ) 36 | "; 37 | 38 | AdomdConnection adomdConnection = new AdomdConnection(connectString); 39 | adomdConnection.Open(); 40 | 41 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection); 42 | AdomdDataReader reader = adomdCommand.ExecuteReader(); 43 | 44 | ConvertReaderToCsv(reader); 45 | 46 | reader.Dispose(); 47 | adomdConnection.Close(); 48 | 49 | } 50 | 51 | static void ConvertReaderToCsv(AdomdDataReader reader, bool openinExcel = true) { 52 | 53 | string csv = string.Empty; 54 | 55 | for (int col = 0; col < reader.FieldCount; col++) { 56 | csv += reader.GetName(col); 57 | csv += (col < (reader.FieldCount - 1)) ? "," : "\n"; 58 | } 59 | 60 | // Create a loop for every row in the resultset 61 | while (reader.Read()) { 62 | // Create a loop for every column in the current row 63 | for (int i = 0; i < reader.FieldCount; i++) { 64 | csv += reader.GetValue(i); 65 | csv += (i < (reader.FieldCount - 1)) ? "," : "\n"; 66 | } 67 | } 68 | 69 | string filePath = System.IO.Directory.GetCurrentDirectory() + @"\QueryResuts.csv"; 70 | StreamWriter writer = File.CreateText(filePath); 71 | writer.Write(csv); 72 | writer.Flush(); 73 | writer.Dispose(); 74 | 75 | if (openinExcel) { 76 | OpenInExcel(filePath); 77 | } 78 | 79 | } 80 | 81 | static void OpenInExcel(string FilePath) { 82 | 83 | ProcessStartInfo startInfo = new ProcessStartInfo(); 84 | 85 | bool excelFound = false; 86 | if (File.Exists("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 87 | startInfo.FileName = "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 88 | excelFound = true; 89 | } else { 90 | if (File.Exists("C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 91 | startInfo.FileName = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 92 | excelFound = true; 93 | } 94 | } 95 | if (excelFound) { 96 | startInfo.Arguments = FilePath; 97 | Process.Start(startInfo); 98 | } else { 99 | System.Console.WriteLine("Coud not find Microsoft Exce on this PC."); 100 | } 101 | 102 | } 103 | 104 | static void AddSalesRegionMeasures() { 105 | 106 | // DAX query to be submitted totabuar database engine 107 | String query = "EVALUATE( VALUES(Customers[Sales Region]) )"; 108 | 109 | AdomdConnection adomdConnection = new AdomdConnection(connectString); 110 | adomdConnection.Open(); 111 | 112 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection); 113 | AdomdDataReader reader = adomdCommand.ExecuteReader(); 114 | 115 | // open connection use TOM to create new measures 116 | TOM.Server server = new TOM.Server(); 117 | server.Connect(connectString); 118 | TOM.Model model = server.Databases[0].Model; 119 | TOM.Table salesTable = model.Tables["Sales"]; 120 | 121 | String measureDescription = "Auto Measures"; 122 | // delete any previously created "Auto" measures 123 | foreach (TOM.Measure m in salesTable.Measures) { 124 | if (m.Description == measureDescription) { 125 | salesTable.Measures.Remove(m); 126 | model.SaveChanges(); 127 | } 128 | } 129 | 130 | // Create the new measures 131 | while (reader.Read()) { 132 | String SalesRegion = reader.GetValue(0).ToString(); 133 | String measureName = $"{SalesRegion} Sales"; 134 | 135 | TOM.Measure measure = new TOM.Measure() { 136 | Name = measureName, 137 | Description = measureDescription, 138 | DisplayFolder = "Auto Measures", 139 | FormatString = "$#,##0", 140 | Expression = $@"CALCULATE( SUM(Sales[SalesAmount]), Customers[Sales Region] = ""{SalesRegion}"" )" 141 | }; 142 | 143 | salesTable.Measures.Add(measure); 144 | } 145 | 146 | model.SaveChanges(); 147 | reader.Dispose(); 148 | adomdConnection.Close(); 149 | 150 | } 151 | } -------------------------------------------------------------------------------- /StarterFiles/Exercise03-Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TOM = Microsoft.AnalysisServices.Tabular; 3 | using Microsoft.AnalysisServices.AdomdClient; 4 | using System.Diagnostics; 5 | using System.IO; 6 | 7 | class Program { 8 | 9 | const string connectString = "Data Source=localhost:50000"; // Update port number on your machine 10 | 11 | static void Main(string[] args) { 12 | 13 | // Exercise 3 - Part 1 14 | ExecuteDaxQuery(); 15 | 16 | // Exercise 3 - Part 2 17 | // AddSalesRegionMeasures(); 18 | } 19 | 20 | static void ExecuteDaxQuery() { 21 | 22 | // DAX query to be submitted totabuar database engine 23 | String query = @" 24 | EVALUATE 25 | SUMMARIZECOLUMNS( 26 | //GROUP BY 27 | Customers[State], 28 | 29 | //FILTER BY 30 | TREATAS( {""Western Region""} , 'Customers'[Sales Region] ) , 31 | 32 | // MEASURES 33 | ""Sales Revenue"" , SUM(Sales[SalesAmount]) , 34 | ""Units Sold"" , SUM(Sales[Quantity]) 35 | ) 36 | "; 37 | 38 | AdomdConnection adomdConnection = new AdomdConnection(connectString); 39 | adomdConnection.Open(); 40 | 41 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection); 42 | AdomdDataReader reader = adomdCommand.ExecuteReader(); 43 | 44 | ConvertReaderToCsv(reader); 45 | 46 | reader.Dispose(); 47 | adomdConnection.Close(); 48 | 49 | } 50 | 51 | static void ConvertReaderToCsv(AdomdDataReader reader, bool openinExcel = true) { 52 | 53 | string csv = string.Empty; 54 | 55 | for (int col = 0; col < reader.FieldCount; col++) { 56 | csv += reader.GetName(col); 57 | csv += (col < (reader.FieldCount - 1)) ? "," : "\n"; 58 | } 59 | 60 | // Create a loop for every row in the resultset 61 | while (reader.Read()) { 62 | // Create a loop for every column in the current row 63 | for (int i = 0; i < reader.FieldCount; i++) { 64 | csv += reader.GetValue(i); 65 | csv += (i < (reader.FieldCount - 1)) ? "," : "\n"; 66 | } 67 | } 68 | 69 | string filePath = System.IO.Directory.GetCurrentDirectory() + @"\QueryResuts.csv"; 70 | StreamWriter writer = File.CreateText(filePath); 71 | writer.Write(csv); 72 | writer.Flush(); 73 | writer.Dispose(); 74 | 75 | if (openinExcel) { 76 | OpenInExcel(filePath); 77 | } 78 | 79 | } 80 | 81 | static void OpenInExcel(string FilePath) { 82 | 83 | ProcessStartInfo startInfo = new ProcessStartInfo(); 84 | 85 | bool excelFound = false; 86 | if (File.Exists("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 87 | startInfo.FileName = "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 88 | excelFound = true; 89 | } 90 | else { 91 | if (File.Exists("C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 92 | startInfo.FileName = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 93 | excelFound = true; 94 | } 95 | } 96 | if (excelFound) { 97 | startInfo.Arguments = FilePath; 98 | Process.Start(startInfo); 99 | } 100 | else { 101 | System.Console.WriteLine("Coud not find Microsoft Exce on this PC."); 102 | } 103 | 104 | } 105 | 106 | static void AddSalesRegionMeasures() { 107 | 108 | // DAX query to be submitted totabuar database engine 109 | String query = "EVALUATE( VALUES(Customers[Sales Region]) )"; 110 | 111 | AdomdConnection adomdConnection = new AdomdConnection(connectString); 112 | adomdConnection.Open(); 113 | 114 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection); 115 | AdomdDataReader reader = adomdCommand.ExecuteReader(); 116 | 117 | // open connection use TOM to create new measures 118 | TOM.Server server = new TOM.Server(); 119 | server.Connect(connectString); 120 | TOM.Model model = server.Databases[0].Model; 121 | TOM.Table salesTable = model.Tables["Sales"]; 122 | 123 | String measureDescription = "Auto Measures"; 124 | // delete any previously created "Auto" measures 125 | foreach (TOM.Measure m in salesTable.Measures) { 126 | if (m.Description == measureDescription) { 127 | salesTable.Measures.Remove(m); 128 | model.SaveChanges(); 129 | } 130 | } 131 | 132 | // Create the new measures 133 | while (reader.Read()) { 134 | String SalesRegion = reader.GetValue(0).ToString(); 135 | String measureName = $"{SalesRegion} Sales"; 136 | 137 | TOM.Measure measure = new TOM.Measure() { 138 | Name = measureName, 139 | Description = measureDescription, 140 | DisplayFolder = "Auto Measures", 141 | FormatString = "$#,##0", 142 | Expression = $@"CALCULATE( SUM(Sales[SalesAmount]), Customers[Sales Region] = ""{SalesRegion}"" )" 143 | }; 144 | 145 | salesTable.Measures.Add(measure); 146 | } 147 | 148 | model.SaveChanges(); 149 | reader.Dispose(); 150 | adomdConnection.Close(); 151 | 152 | } 153 | } -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\dax\calculatedcolumn-agegroup.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | 125 | ..\dax\calculatedcolumn-salesregionsort.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 126 | 127 | 128 | ..\dax\calculatedcolumn-salesregion.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 129 | 130 | 131 | ..\dax\calculatedtable-calendar.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 132 | 133 | 134 | ..\m\customersquery.m;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 135 | 136 | 137 | ..\m\productquery.m;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 138 | 139 | 140 | ..\dax\querygetsalesbystate.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 141 | 142 | 143 | ..\m\salesquery.m;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 144 | 145 | -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Learning_TOM.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Learning_TOM.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to SWITCH ( 65 | /// TRUE(), 66 | /// [Age] >= 65, "65 and over", 67 | /// [Age] >= 50, "50 to 64", 68 | /// [Age] >= 40, "40 to 49", 69 | /// [Age] >= 30, "30 to 39", 70 | /// [Age] >= 18, "18 to 29", 71 | /// [Age] < 18, "Under 18" 72 | ///). 73 | /// 74 | internal static string CalculatedColumn_AgeGroup_dax { 75 | get { 76 | return ResourceManager.GetString("CalculatedColumn_AgeGroup_dax", resourceCulture); 77 | } 78 | } 79 | 80 | /// 81 | /// Looks up a localized string similar to SWITCH( 82 | /// TRUE(), 83 | /// [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"}, 84 | /// "Western Region", 85 | /// [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"}, 86 | /// "Central Region", 87 | /// [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"}, 88 | /// "Eastern Region" 89 | ///). 90 | /// 91 | internal static string CalculatedColumn_SalesRegion_dax { 92 | get { 93 | return ResourceManager.GetString("CalculatedColumn_SalesRegion_dax", resourceCulture); 94 | } 95 | } 96 | 97 | /// 98 | /// Looks up a localized string similar to SWITCH( 99 | /// TRUE(), 100 | /// [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"}, 101 | /// 1, 102 | /// [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"}, 103 | /// 2, 104 | /// [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"}, 105 | /// 3 106 | ///). 107 | /// 108 | internal static string CalculatedColumn_SalesRegionSort_m { 109 | get { 110 | return ResourceManager.GetString("CalculatedColumn_SalesRegionSort_m", resourceCulture); 111 | } 112 | } 113 | 114 | /// 115 | /// Looks up a localized string similar to Var CalenderStart = Date(Year(Min(Sales[InvoiceDate])) , 1, 1) 116 | ///Var CalendarEnd = Date(Year(MAX(Sales[InvoiceDate])), 12, 31) 117 | ///Return 118 | /// CALENDAR(CalenderStart, CalendarEnd) 119 | ///. 120 | /// 121 | internal static string CalculatedTable_Calendar_dax { 122 | get { 123 | return ResourceManager.GetString("CalculatedTable_Calendar_dax", resourceCulture); 124 | } 125 | } 126 | 127 | /// 128 | /// Looks up a localized string similar to let 129 | /// Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"), 130 | /// dbo_Customers = Source{[Schema="dbo",Item="Customers"]}[Data], 131 | /// RemovedOtherColumns = Table.SelectColumns(dbo_Customers,{"CustomerId", "FirstName", "LastName", "City", "State", "Zipcode", "Gender", "BirthDate", "FirstPurchaseDate", "LastPurchaseDate"}), 132 | /// MergedColumns = Table.CombineColumns(RemovedOtherColumns,{"FirstName", "LastName"},Combiner.CombineTextByDelimiter(" ", QuoteStyle.None),"Customer"), 133 | /// Replace [rest of string was truncated]";. 134 | /// 135 | internal static string CustomersQuery_m { 136 | get { 137 | return ResourceManager.GetString("CustomersQuery_m", resourceCulture); 138 | } 139 | } 140 | 141 | /// 142 | /// Looks up a localized string similar to let 143 | /// Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"), 144 | /// dbo_Products = Source{[Schema="dbo",Item="Products"]}[Data], 145 | /// RemovedOtherColumns = Table.SelectColumns(dbo_Products,{"ProductId", "Title", "Description", "ProductCategory", "ProductImageUrl"}), 146 | /// RenamedColumns = Table.RenameColumns(RemovedOtherColumns,{{"Title", "Product"}}), 147 | /// SplitColumnByDelimiter = Table.SplitColumn(RenamedColumns, "ProductCategory", Splitter.SplitTextByDelimiter(" > ", QuoteStyle.Csv), {" [rest of string was truncated]";. 148 | /// 149 | internal static string ProductQuery_m { 150 | get { 151 | return ResourceManager.GetString("ProductQuery_m", resourceCulture); 152 | } 153 | } 154 | 155 | /// 156 | /// Looks up a localized string similar to EVALUATE 157 | /// SUMMARIZECOLUMNS( 158 | /// Customers[State], 159 | /// "Sales Revenue", SUM(Sales[SalesAmount]), 160 | /// "Units Sold", SUM(Sales[Quantity]) 161 | ///). 162 | /// 163 | internal static string QueryGetSalesByState_dax { 164 | get { 165 | return ResourceManager.GetString("QueryGetSalesByState_dax", resourceCulture); 166 | } 167 | } 168 | 169 | /// 170 | /// Looks up a localized string similar to let 171 | /// Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"), 172 | /// dbo_InvoiceDetails = Source{[Schema="dbo",Item="InvoiceDetails"]}[Data], 173 | /// ExpandedInvoices = Table.ExpandRecordColumn(dbo_InvoiceDetails, "Invoices", {"InvoiceDate", "CustomerId"}, {"InvoiceDate", "CustomerId"}), 174 | /// RemovedColumns = Table.RemoveColumns(ExpandedInvoices,{"Products"}), 175 | /// ChangedType = Table.TransformColumnTypes(RemovedColumns,{{"InvoiceDate", type date}, {"SalesAmount", Currency.Type}}) 176 | ///in 177 | /// Ch [rest of string was truncated]";. 178 | /// 179 | internal static string SalesQuery_m { 180 | get { 181 | return ResourceManager.GetString("SalesQuery_m", resourceCulture); 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Demos/DaxFormatterExternalTool/DaxFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | using Microsoft.AnalysisServices.Tabular; 9 | using System.Security.Cryptography; 10 | 11 | namespace DaxFormatterExternalTool 12 | { 13 | 14 | class DaxFormatter 15 | { 16 | 17 | private const string restUrl = "https://daxformatter.azurewebsites.net/api/daxformatter/DaxTextFormat"; 18 | 19 | public static void FormatDaxForModel(Model model) 20 | { 21 | 22 | Console.WriteLine("Iterating measure, calculated columns and calulated tables for DAX formatting..."); 23 | Console.WriteLine(); 24 | 25 | foreach (Table table in model.Tables) 26 | { 27 | 28 | // check which measures require formatting 29 | foreach (var measure in table.Measures) 30 | { 31 | Console.Write("Measure: " + table.Name + "[" + measure.Name + "]"); 32 | if (MeasureRequiresFormatting(measure)) 33 | { 34 | Console.WriteLine(" - formatting DAX..."); 35 | string expressionOwner = measure.Name; 36 | string originalDaxExpression = measure.Expression; 37 | string formattedDaxExpression = DaxFormatter.FormatDaxExpression(originalDaxExpression, expressionOwner); 38 | // write formatted expression back to measure 39 | measure.Expression = formattedDaxExpression; 40 | // store hash of formatted expression for later comparison 41 | string hashedDaxExpression = GetHashValueAsString(formattedDaxExpression); 42 | if (measure.Annotations.Contains("HashedExpression")) 43 | { 44 | measure.Annotations["HashedExpression"].Value = hashedDaxExpression; 45 | } 46 | else 47 | { 48 | measure.Annotations.Add(new Annotation { Name = "HashedExpression", Value = hashedDaxExpression }); 49 | } 50 | } 51 | else 52 | { 53 | Console.WriteLine(" - DAX already formatted"); 54 | } 55 | } 56 | 57 | // check which calculated columns require formatting 58 | foreach (var column in table.Columns) 59 | { 60 | if (column.Type == ColumnType.Calculated) 61 | { 62 | CalculatedColumn col = (CalculatedColumn)column; 63 | Console.Write("Calculated column: " + table.Name + "[" + col.Name + "]"); 64 | if (CalculatedColumnRequiresFormatting(col)) 65 | { 66 | Console.WriteLine(" - formatting DAX..."); 67 | string expressionOwner = "'" + table.Name + "'[" + col.Name + "]"; 68 | string originalDaxExpression = col.Expression; 69 | string formattedDaxExpression = DaxFormatter.FormatDaxExpression(originalDaxExpression, expressionOwner); 70 | // write formatted expression back to calculated column 71 | col.Expression = formattedDaxExpression; 72 | // store hash of formatted expression for later comparison 73 | string hashedDaxExpression = GetHashValueAsString(formattedDaxExpression); 74 | if (col.Annotations.Contains("HashedExpression")) 75 | { 76 | col.Annotations["HashedExpression"].Value = hashedDaxExpression; 77 | } 78 | else 79 | { 80 | col.Annotations.Add(new Annotation { Name = "HashedExpression", Value = hashedDaxExpression }); 81 | } 82 | } 83 | else 84 | { 85 | Console.WriteLine(" - DAX already formatted"); 86 | } 87 | } 88 | 89 | } 90 | 91 | // check which calculated tables require formatting 92 | if ((table.Partitions.Count > 0) && 93 | (table.Partitions[0].SourceType == PartitionSourceType.Calculated)) 94 | { 95 | Console.Write("Calculated table: " + table.Name); 96 | if (CalculatedTableRequiresFormatting(table)) 97 | { 98 | var source = table.Partitions[0].Source as CalculatedPartitionSource; 99 | Console.WriteLine(" - formatting DAX..."); 100 | string expressionOwner = table.Name; 101 | string originalDaxExpression = source.Expression; 102 | string formattedDaxExpression = DaxFormatter.FormatDaxExpression(originalDaxExpression, expressionOwner); 103 | // write formatted expression back to calculated column 104 | source.Expression = formattedDaxExpression; 105 | // store hash of formatted expression for later comparison 106 | string hashedDaxExpression = GetHashValueAsString(formattedDaxExpression); 107 | if (table.Annotations.Contains("HashedExpression")) 108 | { 109 | table.Annotations["HashedExpression"].Value = hashedDaxExpression; 110 | } 111 | else 112 | { 113 | table.Annotations.Add(new Annotation { Name = "HashedExpression", Value = hashedDaxExpression }); 114 | } 115 | 116 | } 117 | else 118 | { 119 | Console.WriteLine(" - DAX already formatted "); 120 | } 121 | } 122 | 123 | } 124 | 125 | model.RequestRefresh(RefreshType.Automatic); 126 | model.SaveChanges(); 127 | 128 | Console.WriteLine(); 129 | Console.WriteLine("Press any key to continue"); 130 | Console.ReadLine(); 131 | } 132 | 133 | private static string FormatDaxExpression(string daxInput, string expressionOwner = "") 134 | { 135 | 136 | string prefix = string.IsNullOrEmpty(expressionOwner) ? "" : expressionOwner + " ="; 137 | 138 | string daxExpression = prefix + daxInput; 139 | 140 | RequestBody requestBody = new RequestBody 141 | { 142 | CallerApp = "Apollo", 143 | Dax = daxExpression, 144 | DecimalSeparator = ".", 145 | ListSeparator = ",", 146 | DatabaseCompatibilityLevel = "520", 147 | SkipSpaceAfterFunctionName = false 148 | }; 149 | 150 | string postBody = JsonConvert.SerializeObject(requestBody); 151 | 152 | HttpContent body = new StringContent(postBody); 153 | body.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json"); 154 | HttpClient client = new HttpClient(); 155 | client.DefaultRequestHeaders.Add("Accept", "application/json; charset=UTF-8"); 156 | HttpResponseMessage response = client.PostAsync(restUrl, body).Result; 157 | 158 | if (response.IsSuccessStatusCode) 159 | { 160 | string jsonResponse = response.Content.ReadAsStringAsync().Result; 161 | ResponseBody responseBody = JsonConvert.DeserializeObject(jsonResponse); 162 | string formattedExression = responseBody.formatted.Replace(prefix, "").Replace("\r\n", "\n"); 163 | return formattedExression; 164 | } 165 | else 166 | { 167 | Console.WriteLine(); 168 | Console.WriteLine("OUCH! - error occurred during POST REST call"); 169 | Console.WriteLine(); 170 | return string.Empty; 171 | } 172 | } 173 | 174 | private static bool CalculatedColumnRequiresFormatting(CalculatedColumn column) 175 | { 176 | if (!column.Annotations.Contains("HashedExpression")) 177 | { 178 | return true; 179 | } 180 | string daxExpression = column.Expression; 181 | string hashedDaxExpression = GetHashValueAsString(daxExpression); 182 | string lastStoredHash = column.Annotations["HashedExpression"].Value; 183 | return hashedDaxExpression != lastStoredHash; 184 | } 185 | 186 | private static bool MeasureRequiresFormatting(Measure measure) 187 | { 188 | if (!measure.Annotations.Contains("HashedExpression")) 189 | { 190 | return true; 191 | } 192 | string daxExpression = measure.Expression; 193 | string hashedDaxExpression = GetHashValueAsString(daxExpression); 194 | string lastStoredHash = measure.Annotations["HashedExpression"].Value; 195 | return hashedDaxExpression != lastStoredHash; 196 | } 197 | 198 | private static bool CalculatedTableRequiresFormatting(Table table) 199 | { 200 | if (!table.Annotations.Contains("HashedExpression")) 201 | { 202 | return true; 203 | } 204 | var source = table.Partitions[0].Source as CalculatedPartitionSource; 205 | string daxExpression = source.Expression; 206 | string hashedDaxExpression = GetHashValueAsString(daxExpression); 207 | string lastStoredHash = table.Annotations["HashedExpression"].Value; 208 | return hashedDaxExpression != lastStoredHash; 209 | } 210 | 211 | private static string GetHashValueAsString(string input) 212 | { 213 | byte[] data = Encoding.UTF8.GetBytes(input); 214 | using (var shaM = new SHA1Managed()) 215 | { 216 | byte[] result = shaM.ComputeHash(data); 217 | return Convert.ToBase64String(result); 218 | } 219 | } 220 | 221 | } 222 | 223 | class RequestBody 224 | { 225 | public string Dax { get; set; } 226 | public object MaxLineLenght { get; set; } 227 | public bool SkipSpaceAfterFunctionName { get; set; } 228 | public string ListSeparator { get; set; } 229 | public string DecimalSeparator { get; set; } 230 | public string CallerApp { get; set; } 231 | public string CallerVersion { get; set; } 232 | public string ServerName { get; set; } 233 | public string ServerEdition { get; set; } 234 | public string ServerType { get; set; } 235 | public string ServerMode { get; set; } 236 | public string ServerLocation { get; set; } 237 | public string ServerVersion { get; set; } 238 | public string DatabaseName { get; set; } 239 | public string DatabaseCompatibilityLevel { get; set; } 240 | } 241 | 242 | class ResponseBody 243 | { 244 | public string formatted { get; set; } 245 | public List errors { get; set; } 246 | } 247 | 248 | 249 | } 250 | -------------------------------------------------------------------------------- /StarterFiles/pbitools.pbitool.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "name": "My PBI Tool", 4 | "description": "Launch PBI Tools to quickly and easily make batch changes to your semantic model.", 5 | "path": "C:\\DevCamp\\Tom-Tutorial\\PBI-Tool\\bin\\Debug\\net5.0\\PBI-Tool.exe", 6 | "arguments": "%server%", 7 | "iconData": "data:image/jpeg;base64,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" 8 | } -------------------------------------------------------------------------------- /Demos/Learning-TOM/Learning-TOM/DatasetManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AMO = Microsoft.AnalysisServices; 3 | using Microsoft.AnalysisServices.Tabular; 4 | 5 | namespace Learning_TOM { 6 | class DatasetManager { 7 | 8 | public static Server server = new Server(); 9 | 10 | public static void ConnectToPowerBIAsServicePrincipal() { 11 | string workspaceConnection = "powerbi://api.powerbi.com/v1.0/myorg/YOUR_WORKSPACE"; 12 | string tenantId = "YOUR_TENANT_ID"; 13 | string appId = "YOUR_APP_ID"; 14 | string appSecret = "YOUR_APP_SECRET"; 15 | string connectStringServicePrincipal = $"DataSource={workspaceConnection};User ID=app:{appId}@{tenantId};Password={appSecret};"; 16 | server.Connect(connectStringServicePrincipal); 17 | } 18 | 19 | public static void ConnectToPowerBIAsUser() { 20 | string workspaceConnection = "powerbi://api.powerbi.com/v1.0/myorg/YOUR_WORKSPACE"; 21 | string userId = "YOUR_USER_NAME"; 22 | string password = "YOUR_USER_PASSWORD"; 23 | string connectStringUser = $"DataSource={workspaceConnection};User ID={userId};Password={password};"; 24 | server.Connect(connectStringUser); 25 | } 26 | 27 | public static void DisplayDatabases() { 28 | foreach (Database database in server.Databases) { 29 | Console.WriteLine(database.Name); 30 | Console.WriteLine(database.CompatibilityLevel); 31 | Console.WriteLine(database.CompatibilityMode); 32 | Console.WriteLine(database.EstimatedSize); 33 | Console.WriteLine(database.ID); 34 | Console.WriteLine(); 35 | } 36 | } 37 | 38 | public static void GetDatabaseInfo(string Name) { 39 | 40 | Database database = server.Databases.GetByName(Name); 41 | 42 | Console.WriteLine("Name: " + database.Name); 43 | Console.WriteLine("ID: " + database.ID); 44 | Console.WriteLine("ModelType: " + database.ModelType); 45 | Console.WriteLine("CompatibilityLevel: " + database.CompatibilityLevel); 46 | Console.WriteLine("LastUpdated: " + database.LastUpdate); 47 | Console.WriteLine("EstimatedSize: " + database.EstimatedSize); 48 | Console.WriteLine("CompatibilityMode: " + database.CompatibilityMode); 49 | Console.WriteLine("LastProcessed: " + database.LastProcessed); 50 | Console.WriteLine("LastSchemaUpdate: " + database.LastSchemaUpdate); 51 | 52 | } 53 | 54 | public static void RefreshDatabaseModel(string Name) { 55 | Database database = server.Databases.GetByName(Name); 56 | database.Model.RequestRefresh(RefreshType.DataOnly); 57 | database.Model.SaveChanges(); 58 | } 59 | 60 | public static Database CreateDatabase(string DatabaseName) { 61 | 62 | string newDatabaseName = server.Databases.GetNewName(DatabaseName); 63 | 64 | var database = new Database() { 65 | Name = newDatabaseName, 66 | ID = newDatabaseName, 67 | CompatibilityLevel = 1520, 68 | StorageEngineUsed = Microsoft.AnalysisServices.StorageEngineUsed.TabularMetadata, 69 | Model = new Model() { 70 | Name = DatabaseName + "-Model", 71 | Description = "A Demo Tabular data model with 1520 compatibility level." 72 | } 73 | }; 74 | 75 | server.Databases.Add(database); 76 | database.Update(Microsoft.AnalysisServices.UpdateOptions.ExpandFull); 77 | 78 | return database; 79 | } 80 | 81 | public static Database CopyDatabase(string sourceDatabaseName, string DatabaseName) { 82 | 83 | Database sourceDatabase = server.Databases.GetByName(sourceDatabaseName); 84 | 85 | string newDatabaseName = server.Databases.GetNewName(DatabaseName); 86 | Database targetDatabase = CreateDatabase(newDatabaseName); 87 | sourceDatabase.Model.CopyTo(targetDatabase.Model); 88 | targetDatabase.Model.SaveChanges(); 89 | 90 | targetDatabase.Model.RequestRefresh(RefreshType.Full); 91 | targetDatabase.Model.SaveChanges(); 92 | 93 | return targetDatabase; 94 | } 95 | 96 | 97 | public static void CreateWingtipSalesModel(Database database) { 98 | 99 | Model model = database.Model; 100 | 101 | Table tableCustomers = CreateCustomersTable(); 102 | Table tableProducts = CreateProductsTable(); 103 | Table tableSales = CreateSalesTable(); 104 | Table tableCalendar = CreateCalendarTable(); 105 | 106 | model.Tables.Add(tableCustomers); 107 | model.Tables.Add(tableProducts); 108 | model.Tables.Add(tableSales); 109 | model.Tables.Add(tableCalendar); 110 | 111 | model.Relationships.Add(new SingleColumnRelationship { 112 | Name = "Customers to Sales", 113 | ToColumn = tableCustomers.Columns["CustomerId"], 114 | ToCardinality = RelationshipEndCardinality.One, 115 | FromColumn = tableSales.Columns["CustomerId"], 116 | FromCardinality = RelationshipEndCardinality.Many 117 | }); 118 | 119 | model.Relationships.Add(new SingleColumnRelationship { 120 | Name = "Products to Sales", 121 | ToColumn = tableProducts.Columns["ProductId"], 122 | ToCardinality = RelationshipEndCardinality.One, 123 | FromColumn = tableSales.Columns["ProductId"], 124 | FromCardinality = RelationshipEndCardinality.Many 125 | }); 126 | 127 | model.Relationships.Add(new SingleColumnRelationship { 128 | Name = "Calendar to Sales", 129 | ToColumn = tableCalendar.Columns["DateKey"], 130 | ToCardinality = RelationshipEndCardinality.One, 131 | FromColumn = tableSales.Columns["DateKey"], 132 | FromCardinality = RelationshipEndCardinality.Many 133 | }); 134 | 135 | model.SaveChanges(); 136 | 137 | model.RequestRefresh(RefreshType.Full); 138 | model.SaveChanges(); 139 | } 140 | 141 | private static Table CreateCustomersTable() { 142 | 143 | Table customersTable = new Table() { 144 | Name = "Customers", 145 | Description = "Customers table", 146 | Partitions = { 147 | new Partition() { 148 | Name = "All Customers", 149 | Mode = ModeType.Import, 150 | Source = new MPartitionSource() { 151 | Expression=Properties.Resources.CustomersQuery_m 152 | } 153 | } 154 | }, 155 | Columns = { 156 | new DataColumn() { Name = "CustomerId", DataType = DataType.Int64, SourceColumn = "CustomerId", IsHidden=true }, 157 | new DataColumn() { Name = "Customer", DataType = DataType.String, SourceColumn = "Customer" }, 158 | new DataColumn() { Name = "State", DataType = DataType.String, SourceColumn = "State", DataCategory="StateOrProvince" }, 159 | new DataColumn() { Name = "City", DataType = DataType.String, SourceColumn = "City", DataCategory="Place" }, 160 | new DataColumn() { Name = "City Name", DataType = DataType.String, SourceColumn = "City Name" }, 161 | new DataColumn() { Name = "Zipcode", DataType = DataType.String, SourceColumn = "Zipcode", DataCategory="PostalCode" }, 162 | new DataColumn() { Name = "BirthDate", DataType = DataType.DateTime, SourceColumn = "BirthDate", IsHidden=true }, 163 | new DataColumn() { Name = "Gender", DataType = DataType.String, SourceColumn = "Gender" }, 164 | new DataColumn() { Name = "Customer Type", DataType = DataType.String, SourceColumn = "Customer Type" }, 165 | new CalculatedColumn() { Name="Age", Expression = "Floor( (TODAY()-Customers[BirthDate])/365, 1)", IsHidden=true, SummarizeBy=AggregateFunction.Average }, 166 | new CalculatedColumn() { Name="Age Group", Expression = Properties.Resources.CalculatedColumn_AgeGroup_dax }, 167 | new CalculatedColumn() { Name="Sales Region", Expression = Properties.Resources.CalculatedColumn_SalesRegion_dax }, 168 | new CalculatedColumn() { Name="SalesRegionSort", Expression = Properties.Resources.CalculatedColumn_SalesRegionSort_m, DataType=DataType.Int64, IsHidden=true } 169 | } 170 | }; 171 | 172 | customersTable.Columns["Sales Region"].SortByColumn = customersTable.Columns["SalesRegionSort"]; 173 | 174 | customersTable.Hierarchies.Add( 175 | new Hierarchy() { 176 | Name = "Customer Geography", 177 | Levels = { 178 | new Level() { Ordinal=0, Name="Sales Region", Column=customersTable.Columns["Sales Region"] }, 179 | new Level() { Ordinal=1, Name="State", Column=customersTable.Columns["State"] }, 180 | new Level() { Ordinal=2, Name="City", Column=customersTable.Columns["City"] }, 181 | new Level() { Ordinal=3, Name="Zipcode", Column=customersTable.Columns["Zipcode"] } 182 | } 183 | }); 184 | 185 | return customersTable; 186 | } 187 | 188 | private static Table CreateProductsTable() { 189 | 190 | Table productsTable = new Table() { 191 | Name = "Products", 192 | Description = "Products table", 193 | Partitions = { 194 | new Partition() { 195 | Name = "All Products", 196 | Mode = ModeType.Import, 197 | Source = new MPartitionSource() { 198 | Expression = Properties.Resources.ProductQuery_m 199 | } 200 | } 201 | }, 202 | Columns = { 203 | new DataColumn() { Name = "ProductId", DataType = DataType.Int64, SourceColumn = "ProductId", IsHidden = true }, 204 | new DataColumn() { Name = "Product", DataType = DataType.String, SourceColumn = "Product" }, 205 | new DataColumn() { Name = "Description", DataType = DataType.String, SourceColumn = "Description" }, 206 | new DataColumn() { Name = "Category", DataType = DataType.String, SourceColumn = "Category" }, 207 | new DataColumn() { Name = "Subcategory", DataType = DataType.String, SourceColumn = "Subcategory" }, 208 | new DataColumn() { Name = "Product Image", DataType = DataType.String, SourceColumn = "ProductImageUrl", DataCategory = "ImageUrl" } 209 | } 210 | }; 211 | 212 | productsTable.Hierarchies.Add( 213 | new Hierarchy() { 214 | Name = "Product Category", 215 | Levels = { 216 | new Level() { Ordinal=0, Name="Category", Column=productsTable.Columns["Category"] }, 217 | new Level() { Ordinal=1, Name="Subcategory", Column=productsTable.Columns["Subcategory"] }, 218 | new Level() { Ordinal=2, Name="Product", Column=productsTable.Columns["Product"] } 219 | } 220 | }); 221 | 222 | return productsTable; 223 | } 224 | 225 | private static Table CreateSalesTable() { 226 | 227 | return new Table() { 228 | Name = "Sales", 229 | Description = "Sales table", 230 | Partitions = { 231 | new Partition() { 232 | Name = "All Sales", 233 | Mode = ModeType.Import, 234 | Source = new MPartitionSource() { 235 | Expression=Properties.Resources.SalesQuery_m 236 | } 237 | } 238 | }, 239 | Columns = { 240 | new DataColumn() { Name = "Id", DataType = DataType.Int64, SourceColumn = "Id", IsHidden=true }, 241 | new DataColumn() { Name = "Quantity", DataType = DataType.Int64, SourceColumn = "Quantity", IsHidden=true }, 242 | new DataColumn() { Name = "SalesAmount", DataType = DataType.Decimal, SourceColumn = "SalesAmount", IsHidden=true }, 243 | new DataColumn() { Name = "InvoiceId", DataType = DataType.Int64, SourceColumn = "InvoiceId", IsHidden=true }, 244 | new DataColumn() { Name = "CustomerId", DataType = DataType.Int64, SourceColumn = "CustomerId", IsHidden=true }, 245 | new DataColumn() { Name = "ProductId", DataType = DataType.Int64, SourceColumn = "ProductId", IsHidden=true }, 246 | new DataColumn() { Name = "InvoiceDate", DataType = DataType.DateTime, SourceColumn = "InvoiceDate", IsHidden=true }, 247 | new CalculatedColumn() { Name="DateKey", DataType = DataType.Int64, IsHidden=true, Expression="Year([InvoiceDate])*10000 + Month([InvoiceDate])*100 + Day([InvoiceDate])" } 248 | }, 249 | Measures = { 250 | new Measure { Name = "Sales Revenue", Expression = "Sum(Sales[SalesAmount])", FormatString=@"\$#,0;(\$#,0);\$#,0" }, 251 | new Measure { Name = "Units Sold", Expression = "Sum(Sales[Quantity])", FormatString="#,0" }, 252 | new Measure { Name = "Customer Count", Expression = "CountRows(Customers)", FormatString="#,0" } 253 | } 254 | }; 255 | } 256 | 257 | private static Table CreateCalendarTable() { 258 | 259 | Table calendarTable = new Table { 260 | Name = "Calendar", 261 | Partitions = { 262 | new Partition { 263 | Source = new CalculatedPartitionSource { 264 | Expression = Properties.Resources.CalculatedTable_Calendar_dax, 265 | } 266 | } 267 | }, 268 | Columns = { 269 | new DataColumn() { Name = "Date", DataType = DataType.DateTime, SourceColumn = "Date", FormatString="MM/dd/yyyy" }, 270 | new CalculatedColumn() { Name = "DateKey", DataType = DataType.Int64, IsHidden = true ,Expression = "Year([Date])*10000 + Month([Date])*100 + Day([Date])" }, 271 | new CalculatedColumn() { Name = "Year", DataType = DataType.Int64, Expression = "Year([Date])", SummarizeBy=AggregateFunction.None }, 272 | new CalculatedColumn() { Name = "Quarter", DataType = DataType.String, Expression = @"Year([Date]) & ""-Q"" & FORMAT([Date], ""q"")" }, 273 | new CalculatedColumn() { Name = "Month", DataType = DataType.String, Expression = @"FORMAT([Date], ""MMM yyyy"")", }, 274 | new CalculatedColumn() { Name = "MonthSort", DataType = DataType.String, Expression = @"Format([Date], ""yyyy-MM"")", IsHidden = true }, 275 | new CalculatedColumn() { Name = "Month in Year", DataType = DataType.String, Expression = @"FORMAT([Date], ""MMM"")" }, 276 | new CalculatedColumn() { Name = "MonthInYearSort", DataType = DataType.Int64, Expression = "MONTH([Date])", IsHidden = true }, 277 | new CalculatedColumn() { Name = "Day of Week", DataType = DataType.String, Expression = @"FORMAT([Date], ""ddd"")" }, 278 | new CalculatedColumn() { Name = "DayOfWeekSort", DataType = DataType.Int64, Expression = "WEEKDAY([Date], 2)", IsHidden = true } 279 | } 280 | }; 281 | 282 | calendarTable.Columns["Month"].SortByColumn = calendarTable.Columns["MonthSort"]; 283 | calendarTable.Columns["Month in Year"].SortByColumn = calendarTable.Columns["MonthInYearSort"]; 284 | calendarTable.Columns["Day of Week"].SortByColumn = calendarTable.Columns["DayOfWeekSort"]; 285 | 286 | calendarTable.Hierarchies.Add( 287 | new Hierarchy() { 288 | Name = "Calendar Drilldown", 289 | Levels = { 290 | new Level() { Ordinal=0, Name="Year", Column=calendarTable.Columns["Year"] }, 291 | new Level() { Ordinal=1, Name="Quarter", Column=calendarTable.Columns["Quarter"] }, 292 | new Level() { Ordinal=2, Name="Month", Column=calendarTable.Columns["Month"] } 293 | } 294 | }); 295 | 296 | return calendarTable; 297 | } 298 | 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Solution/Exercise04/PBI-Tool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | using Microsoft.AnalysisServices.AdomdClient; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Collections.Generic; 7 | using System.Text.RegularExpressions; 8 | 9 | class Program { 10 | 11 | static Server server; 12 | static string consoleDelimeter = "╠═══════════════════════════════════════════════════════════════════════════════╣"; 13 | static string consoleHeader = "║"; 14 | 15 | static string appFolder = Environment.CurrentDirectory + "\\"; 16 | 17 | static void Main(string[] args) { 18 | 19 | Model model; 20 | 21 | Console.BackgroundColor = ConsoleColor.DarkBlue; 22 | Console.ForegroundColor = ConsoleColor.White; 23 | Console.Clear(); 24 | 25 | server = new Server(); 26 | if (args.Length > 0) { 27 | string pbiserver = args[0].ToString(); 28 | server.Connect(pbiserver); 29 | model = server.Databases[0].Model; 30 | } 31 | else { 32 | setServer(); 33 | model = setModel(); 34 | } 35 | 36 | string userInput = ""; 37 | while (userInput != "0") { 38 | Console.WriteLine(consoleDelimeter); 39 | myConsoleWriteLine($"Server : {server.ConnectionString}"); 40 | myConsoleWriteLine($"Database: {model.Database.Name}"); 41 | Console.WriteLine(consoleDelimeter); 42 | myConsoleWriteLine($" 0 Exit"); 43 | myConsoleWriteLine($""); 44 | myConsoleWriteLine($" 1 List Tables Storage Modes"); 45 | myConsoleWriteLine($" 2 List Partition Queries"); 46 | 47 | myConsoleWriteLine($" 4 List Tables"); 48 | myConsoleWriteLine($" 5 Run DAX query"); 49 | 50 | myConsoleWriteLine($" 7 Set Database"); 51 | myConsoleWriteLine($" 8 Set Server"); 52 | 53 | myConsoleWriteLine($" 9 List Table Processed state"); 54 | myConsoleWriteLine($" 10 Get Database as TMSL"); 55 | myConsoleWriteLine($" 11 Get Table as TMSL"); 56 | Console.WriteLine(consoleDelimeter); 57 | 58 | 59 | Console.CursorVisible = true; 60 | Console.CursorSize = 100; // Emphasize the cursor. 61 | userInput = Console.ReadLine(); 62 | 63 | switch (userInput) { 64 | case "1": 65 | getStorageMode(model); 66 | break; 67 | case "2": 68 | getPartitionQueries(model); 69 | break; 70 | case "4": 71 | getTables(model); 72 | break; 73 | case "5": 74 | executeDAX(model); 75 | break; 76 | case "7": 77 | model = setModel(); 78 | break; 79 | case "8": 80 | setServer(); 81 | model = setModel(); 82 | break; 83 | 84 | 85 | case "9": 86 | getTableProcessedState(model); 87 | break; 88 | case "10": 89 | getModelTMSL(model); 90 | break; 91 | case "11": 92 | getTableTMSL(model); 93 | break; 94 | default: 95 | Console.WriteLine($"You chose option: {userInput}"); 96 | break; 97 | } 98 | } 99 | } 100 | 101 | 102 | static void myConsoleWriteLine(string s) { 103 | Console.WriteLine("{0,0} {1,-76} {0,0}", consoleHeader, s); 104 | } 105 | 106 | static private void getStorageMode(Model model) { 107 | foreach (Table table in model.Tables) { 108 | myConsoleWriteLine( 109 | String.Format( 110 | " {0,-30}{1,-20}", 111 | table.Name, 112 | table.Partitions[0].Mode 113 | ) 114 | ); 115 | } 116 | } 117 | 118 | static private void getTableTMSL(Model model) { 119 | 120 | int i = 0; 121 | foreach (Table table in model.Tables) { 122 | Console.WriteLine($"{i,4} - {table.Name,-30}"); 123 | i++; 124 | } 125 | 126 | String s = Console.ReadLine(); 127 | int tableIndex = int.Parse(s); 128 | 129 | Table selectedTable = model.Tables[tableIndex]; 130 | 131 | String tmslTable = Microsoft.AnalysisServices.Tabular.JsonSerializer.SerializeObject(selectedTable); 132 | String tmsl = $"{{\"createOrReplace\": {{\"object\": {{\"database\": \"{model.Database.Name}\",\"table\": \"{selectedTable.Name}\" }},\"table\": {tmslTable}}}}}"; 133 | 134 | String tmslResultFileName = $"{appFolder}TMSLResult-{Guid.NewGuid()}.tsv"; 135 | File.AppendAllText(tmslResultFileName, tmsl); 136 | ProcessStartInfo startInfo = new ProcessStartInfo(); 137 | startInfo.FileName = "NOTEPAD.EXE"; 138 | startInfo.Arguments = tmslResultFileName; 139 | Process.Start(startInfo); 140 | } 141 | 142 | static private void getPartitionQueries(Model model) { 143 | foreach (Table table in model.Tables) { 144 | switch (table.Partitions[0].SourceType) { 145 | case PartitionSourceType.Query: 146 | QueryPartitionSource queryPartitionSource = (QueryPartitionSource)table.Partitions[0].Source; 147 | myConsoleWriteLine($"Table = {table.Name} - Query = {queryPartitionSource.Query}"); 148 | break; 149 | case PartitionSourceType.M: 150 | MPartitionSource mPartitionSource = (MPartitionSource)table.Partitions[0].Source; 151 | myConsoleWriteLine($"Table = {table.Name} - Expression = {mPartitionSource.Expression}"); 152 | break; 153 | } 154 | } 155 | } 156 | 157 | static private void getTablesAsObjects(Model model) { 158 | String objectTMSL = ""; 159 | int i = 0; 160 | foreach (Table table in model.Tables) { 161 | if (i > 0) 162 | objectTMSL += ",\n"; 163 | 164 | objectTMSL += $"\t{{\n\t\"database\": \"{model.Database.Name}\",\n\t\"table\": \"{table.Name}\"\n\t}}"; 165 | i++; 166 | } 167 | 168 | openInNotePad(objectTMSL); 169 | } 170 | 171 | static private void openInNotePad(String s) { 172 | String tmslResultFileName = $"{appFolder}TMSLResult-{Guid.NewGuid()}.tsv"; 173 | File.AppendAllText(tmslResultFileName, s); 174 | ProcessStartInfo startInfo = new ProcessStartInfo(); 175 | startInfo.FileName = "NOTEPAD.EXE"; 176 | startInfo.Arguments = tmslResultFileName; 177 | Process.Start(startInfo); 178 | } 179 | 180 | static private void getModelTMSL(Model model) { 181 | String tmslDB = Microsoft.AnalysisServices.Tabular.JsonSerializer.SerializeDatabase(server.Databases[0]); 182 | String tmsl = $"{{\"createOrReplace\": {{\"object\": {{\"database\": \"{model.Database.Name}\"}},\"database\": {tmslDB}}}}}"; 183 | String tmslResultFileName = $"{appFolder}TMSLResult-{Guid.NewGuid()}.tsv"; 184 | File.AppendAllText(tmslResultFileName, tmsl); 185 | ProcessStartInfo startInfo = new ProcessStartInfo(); 186 | startInfo.FileName = "NOTEPAD.EXE"; 187 | startInfo.Arguments = tmslResultFileName; 188 | Process.Start(startInfo); 189 | } 190 | 191 | static private void getTables(Model model) { 192 | Console.WriteLine(consoleDelimeter); 193 | myConsoleWriteLine($" 1 Display to Screen"); 194 | myConsoleWriteLine($" 2 Open as TMSL"); 195 | Console.WriteLine(consoleDelimeter); 196 | String userInput = Console.ReadLine(); 197 | if (userInput == "2") { 198 | getTablesAsObjects(model); 199 | } 200 | else { 201 | foreach (Table table in model.Tables) { 202 | myConsoleWriteLine($" Table = {table.Name}"); 203 | } 204 | } 205 | } 206 | 207 | static private void setServer() { 208 | string serverName = ""; 209 | Console.Clear(); 210 | Console.WriteLine(consoleDelimeter); 211 | myConsoleWriteLine("Enter or Pick a Server"); 212 | serverName = getData("Servers.dat"); 213 | if (server.Connected) { 214 | server.Disconnect(); 215 | } 216 | try { 217 | server.Connect(serverName); 218 | if (!serverName.ToUpper().StartsWith("LOCALHOST")) { 219 | saveData(appFolder + "Servers.dat", serverName); 220 | } 221 | Console.Clear(); 222 | } 223 | catch (Exception ex) { 224 | Console.WriteLine($"{ex.InnerException.ToString()}"); 225 | } 226 | } 227 | 228 | static private Dictionary setDBIndex() { 229 | 230 | Dictionary databases = new Dictionary(); 231 | int dbIndex = 0; 232 | 233 | foreach (Database database in server.Databases) { 234 | string databaseName = database.Name; 235 | if (!databases.ContainsValue(databaseName)) { 236 | databases.Add(dbIndex, databaseName); 237 | dbIndex++; 238 | } 239 | } 240 | return databases; 241 | } 242 | 243 | static private Model setModel() { 244 | Model model = null; 245 | if (server.Databases.Count == 1) { 246 | model = server.Databases[0].Model; 247 | } 248 | else { 249 | Dictionary x = setDBIndex(); 250 | Console.Clear(); 251 | Console.WriteLine(consoleDelimeter); 252 | myConsoleWriteLine($"Please pick a database:"); 253 | Console.WriteLine(consoleDelimeter); 254 | foreach (KeyValuePair database in x) { 255 | myConsoleWriteLine($" {database.Key} - {database.Value}"); 256 | } 257 | Console.WriteLine(consoleDelimeter); 258 | String s = Console.ReadLine(); 259 | int dbIndex = int.Parse(s); 260 | 261 | if (x.ContainsKey(int.Parse(s))) { 262 | string db; 263 | x.TryGetValue(dbIndex, out db); 264 | model = server.Databases[db].Model; 265 | } 266 | Console.Clear(); 267 | } 268 | return model; 269 | } 270 | 271 | static private void showConsoleHeader(Model model) { 272 | Console.WriteLine(consoleDelimeter); 273 | myConsoleWriteLine($"Server : {server.ConnectionString}"); 274 | myConsoleWriteLine($"Database: {model.Database.Name}"); 275 | Console.WriteLine(consoleDelimeter); 276 | } 277 | 278 | static private void getTableProcessedState(Model model) { 279 | foreach (Table table in model.Tables) { 280 | foreach (Partition partition in table.Partitions) { 281 | String s = String.Format("{0,-30}{1,-30}{2,-20}", 282 | table.Name, 283 | partition.Name, 284 | partition.State.ToString() ); 285 | myConsoleWriteLine(s); 286 | } 287 | } 288 | } 289 | 290 | static private string getData(String filename) { 291 | filename = appFolder + filename; 292 | Console.WriteLine(consoleDelimeter); 293 | if (!System.IO.File.Exists(filename)) { 294 | System.IO.File.AppendAllText(filename, ""); 295 | } 296 | String[] lastData = System.IO.File.ReadAllLines(filename); 297 | if (filename.Contains("Server")) { 298 | List p = new List(); 299 | p.AddRange(lastData); 300 | p.AddRange(getProcesses()); 301 | String[] x = p.ToArray(); 302 | lastData = p.ToArray(); 303 | } 304 | 305 | int i = 0; 306 | foreach (string q in lastData) { 307 | myConsoleWriteLine($"{i} {q}"); 308 | i++; 309 | } 310 | 311 | Console.WriteLine(consoleDelimeter); 312 | 313 | String userInput = Console.ReadLine(); 314 | 315 | if (userInput.ToUpper().StartsWith("DEL")) { 316 | //TODO: delete code here 317 | ProcessStartInfo startInfo = new ProcessStartInfo(); 318 | startInfo.FileName = "NOTEPAD.EXE"; 319 | startInfo.Arguments = filename; 320 | Process.Start(startInfo); 321 | } 322 | else { 323 | String[] lines = { userInput }; 324 | if (Int32.TryParse(userInput, out int queryNumber)) { 325 | userInput = lastData[queryNumber]; 326 | } 327 | else { 328 | saveData(filename, userInput); 329 | } 330 | } 331 | return userInput; 332 | } 333 | 334 | public static string LookupProcess(int pid) { 335 | string procName; 336 | try { procName = Process.GetProcessById(pid).ProcessName; } 337 | catch (Exception) { procName = "-"; } 338 | return procName; 339 | } 340 | 341 | static private List getProcesses() { 342 | Process p = new Process(); 343 | ProcessStartInfo ps = new ProcessStartInfo(); 344 | ps.Arguments = "-ano"; 345 | ps.FileName = "netstat.exe"; 346 | ps.UseShellExecute = false; 347 | ps.WindowStyle = ProcessWindowStyle.Hidden; 348 | ps.RedirectStandardInput = true; 349 | ps.RedirectStandardOutput = true; 350 | ps.RedirectStandardError = true; 351 | p.StartInfo = ps; 352 | p.Start(); 353 | StreamReader stdOutput = p.StandardOutput; 354 | StreamReader stdError = p.StandardError; 355 | string content = stdOutput.ReadToEnd() + stdError.ReadToEnd(); 356 | string exitStatus = p.ExitCode.ToString(); 357 | // var Ports = new List(); 358 | List myPorts = new List { }; 359 | string[] rows = Regex.Split(content, "\r\n"); 360 | foreach (string row in rows) { 361 | //Split it baby 362 | string[] tokens = Regex.Split(row, "\\s+"); 363 | if (tokens.Length > 4 && (tokens[1].Equals("UDP") || tokens[1].Equals("TCP"))) { 364 | string localAddress = Regex.Replace(tokens[2], @"\[(.*?)\]", "1.1.1.1"); 365 | string pname = tokens[1] == "UDP" ? LookupProcess(Convert.ToInt32(tokens[4])) : LookupProcess(Convert.ToInt32(tokens[5])); 366 | string pnumber = "localhost:" + localAddress.Split(':')[1]; 367 | if (pname == "msmdsrv" && !myPorts.Contains(pnumber)) { 368 | myPorts.Add(pnumber); 369 | } 370 | } 371 | } 372 | return myPorts; 373 | } 374 | 375 | static private void saveData(String filename, String line) { 376 | String[] lines = System.IO.File.ReadAllLines(filename); 377 | 378 | bool lineFound = false; 379 | foreach (string l in lines) { 380 | if (l == line) { 381 | lineFound = true; 382 | } 383 | } 384 | 385 | string[] writeThis = { line }; 386 | if (!lineFound) { 387 | System.IO.File.AppendAllLines(filename, writeThis); 388 | } 389 | } 390 | 391 | 392 | static private void executeDAX(Model model) { 393 | Console.Clear(); 394 | showConsoleHeader(model); 395 | myConsoleWriteLine($"Enter or Pick a Query"); 396 | string query = getData("Query.dat"); 397 | AdomdConnection adomdConnection = new AdomdConnection($"Data Source={model.Database.Parent.ConnectionString};Initial catalog={model.Database.Name}"); 398 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection); 399 | adomdConnection.Open(); 400 | String queryResultFileName = $"{appFolder}QueryResult-{Guid.NewGuid()}.tsv"; 401 | List list = new List(); 402 | bool hasHeader = false; 403 | try { 404 | AdomdDataReader reader = adomdCommand.ExecuteReader(); 405 | while (reader.Read()) { 406 | String rowResults = ""; 407 | /***************************************************************************** 408 | Add Header (if needed) 409 | ****************************************************************************/ 410 | if (!hasHeader) { 411 | for (int columnNumber = 0; columnNumber < reader.FieldCount; columnNumber++ ) { 412 | if (columnNumber > 0) { 413 | rowResults += $"\t"; 414 | } 415 | rowResults += $"{reader.GetName(columnNumber)}"; 416 | } 417 | Console.WriteLine(rowResults); 418 | list.Add(rowResults); 419 | hasHeader = true; 420 | } 421 | /***************************************************************************** 422 | Add normal line 423 | ****************************************************************************/ 424 | rowResults = ""; 425 | // Create a loop for every column in the current row 426 | for (int columnNumber = 0; columnNumber < reader.FieldCount; columnNumber++ ) { 427 | if (columnNumber > 0) { 428 | rowResults += $"\t"; 429 | } 430 | rowResults += $"{reader.GetValue(columnNumber)}"; 431 | } 432 | Console.WriteLine(rowResults); 433 | list.Add(rowResults); 434 | } 435 | 436 | System.IO.File.WriteAllLines(queryResultFileName, list); 437 | ProcessStartInfo startInfo = new ProcessStartInfo(); 438 | bool excelFound = false; 439 | if (File.Exists("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 440 | startInfo.FileName = "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 441 | excelFound = true; 442 | } 443 | else { 444 | if (File.Exists("C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 445 | startInfo.FileName = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 446 | excelFound = true; 447 | } 448 | } 449 | 450 | if (excelFound) { 451 | startInfo.Arguments = queryResultFileName; 452 | Process.Start(startInfo); 453 | } 454 | 455 | } 456 | catch (Exception ex) { 457 | Console.WriteLine(ex.Message); 458 | Console.ReadKey(); 459 | } 460 | adomdConnection.Close(); 461 | } 462 | } -------------------------------------------------------------------------------- /StarterFiles/Exercise04-Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AnalysisServices.Tabular; 3 | using Microsoft.AnalysisServices.AdomdClient; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Collections.Generic; 7 | using System.Text.RegularExpressions; 8 | 9 | class Program { 10 | 11 | static Server server; 12 | static string consoleDelimeter = "╠═══════════════════════════════════════════════════════════════════════════════╣"; 13 | static string consoleHeader = "║"; 14 | 15 | static string appFolder = Environment.CurrentDirectory + "\\"; 16 | 17 | static void Main(string[] args) { 18 | 19 | Model model; 20 | 21 | Console.BackgroundColor = ConsoleColor.DarkBlue; 22 | Console.ForegroundColor = ConsoleColor.White; 23 | Console.Clear(); 24 | 25 | server = new Server(); 26 | if (args.Length > 0) { 27 | string pbiserver = args[0].ToString(); 28 | server.Connect(pbiserver); 29 | model = server.Databases[0].Model; 30 | } 31 | else { 32 | setServer(); 33 | model = setModel(); 34 | } 35 | 36 | string userInput = ""; 37 | while (userInput != "0") { 38 | Console.WriteLine(consoleDelimeter); 39 | myConsoleWriteLine($"Server : {server.ConnectionString}"); 40 | myConsoleWriteLine($"Database: {model.Database.Name}"); 41 | Console.WriteLine(consoleDelimeter); 42 | myConsoleWriteLine($" 0 Exit"); 43 | myConsoleWriteLine($""); 44 | myConsoleWriteLine($" 1 List Tables Storage Modes"); 45 | myConsoleWriteLine($" 2 List Partition Queries"); 46 | 47 | myConsoleWriteLine($" 4 List Tables"); 48 | myConsoleWriteLine($" 5 Run DAX query"); 49 | 50 | myConsoleWriteLine($" 7 Set Database"); 51 | myConsoleWriteLine($" 8 Set Server"); 52 | 53 | myConsoleWriteLine($" 9 List Table Processed state"); 54 | myConsoleWriteLine($" 10 Get Database as TMSL"); 55 | myConsoleWriteLine($" 11 Get Table as TMSL"); 56 | Console.WriteLine(consoleDelimeter); 57 | 58 | 59 | Console.CursorVisible = true; 60 | Console.CursorSize = 100; // Emphasize the cursor. 61 | userInput = Console.ReadLine(); 62 | 63 | switch (userInput) { 64 | case "1": 65 | getStorageMode(model); 66 | break; 67 | case "2": 68 | getPartitionQueries(model); 69 | break; 70 | case "4": 71 | getTables(model); 72 | break; 73 | case "5": 74 | executeDAX(model); 75 | break; 76 | case "7": 77 | model = setModel(); 78 | break; 79 | case "8": 80 | setServer(); 81 | model = setModel(); 82 | break; 83 | 84 | 85 | case "9": 86 | getTableProcessedState(model); 87 | break; 88 | case "10": 89 | getModelTMSL(model); 90 | break; 91 | case "11": 92 | getTableTMSL(model); 93 | break; 94 | default: 95 | Console.WriteLine($"You chose option: {userInput}"); 96 | break; 97 | } 98 | } 99 | } 100 | 101 | 102 | static void myConsoleWriteLine(string s) { 103 | Console.WriteLine("{0,0} {1,-76} {0,0}", consoleHeader, s); 104 | } 105 | 106 | static private void getStorageMode(Model model) { 107 | foreach (Table table in model.Tables) { 108 | myConsoleWriteLine( 109 | String.Format( 110 | " {0,-30}{1,-20}", 111 | table.Name, 112 | table.Partitions[0].Mode 113 | ) 114 | ); 115 | } 116 | } 117 | 118 | static private void getTableTMSL(Model model) { 119 | 120 | int i = 0; 121 | foreach (Table table in model.Tables) { 122 | Console.WriteLine($"{i,4} - {table.Name,-30}"); 123 | i++; 124 | } 125 | 126 | String s = Console.ReadLine(); 127 | int tableIndex = int.Parse(s); 128 | 129 | Table selectedTable = model.Tables[tableIndex]; 130 | 131 | String tmslTable = Microsoft.AnalysisServices.Tabular.JsonSerializer.SerializeObject(selectedTable); 132 | String tmsl = $"{{\"createOrReplace\": {{\"object\": {{\"database\": \"{model.Database.Name}\",\"table\": \"{selectedTable.Name}\" }},\"table\": {tmslTable}}}}}"; 133 | 134 | String tmslResultFileName = $"{appFolder}TMSLResult-{Guid.NewGuid()}.tsv"; 135 | File.AppendAllText(tmslResultFileName, tmsl); 136 | ProcessStartInfo startInfo = new ProcessStartInfo(); 137 | startInfo.FileName = "NOTEPAD.EXE"; 138 | startInfo.Arguments = tmslResultFileName; 139 | Process.Start(startInfo); 140 | } 141 | 142 | static private void getPartitionQueries(Model model) { 143 | foreach (Table table in model.Tables) { 144 | switch (table.Partitions[0].SourceType) { 145 | case PartitionSourceType.Query: 146 | QueryPartitionSource queryPartitionSource = (QueryPartitionSource)table.Partitions[0].Source; 147 | myConsoleWriteLine($"Table = {table.Name} - Query = {queryPartitionSource.Query}"); 148 | break; 149 | case PartitionSourceType.M: 150 | MPartitionSource mPartitionSource = (MPartitionSource)table.Partitions[0].Source; 151 | myConsoleWriteLine($"Table = {table.Name} - Expression = {mPartitionSource.Expression}"); 152 | break; 153 | } 154 | } 155 | } 156 | 157 | static private void getTablesAsObjects(Model model) { 158 | String objectTMSL = ""; 159 | int i = 0; 160 | foreach (Table table in model.Tables) { 161 | if (i > 0) 162 | objectTMSL += ",\n"; 163 | 164 | objectTMSL += $"\t{{\n\t\"database\": \"{model.Database.Name}\",\n\t\"table\": \"{table.Name}\"\n\t}}"; 165 | i++; 166 | } 167 | 168 | openInNotePad(objectTMSL); 169 | } 170 | 171 | static private void openInNotePad(String s) { 172 | String tmslResultFileName = $"{appFolder}TMSLResult-{Guid.NewGuid()}.tsv"; 173 | File.AppendAllText(tmslResultFileName, s); 174 | ProcessStartInfo startInfo = new ProcessStartInfo(); 175 | startInfo.FileName = "NOTEPAD.EXE"; 176 | startInfo.Arguments = tmslResultFileName; 177 | Process.Start(startInfo); 178 | } 179 | 180 | static private void getModelTMSL(Model model) { 181 | String tmslDB = Microsoft.AnalysisServices.Tabular.JsonSerializer.SerializeDatabase(server.Databases[0]); 182 | String tmsl = $"{{\"createOrReplace\": {{\"object\": {{\"database\": \"{model.Database.Name}\"}},\"database\": {tmslDB}}}}}"; 183 | String tmslResultFileName = $"{appFolder}TMSLResult-{Guid.NewGuid()}.tsv"; 184 | File.AppendAllText(tmslResultFileName, tmsl); 185 | ProcessStartInfo startInfo = new ProcessStartInfo(); 186 | startInfo.FileName = "NOTEPAD.EXE"; 187 | startInfo.Arguments = tmslResultFileName; 188 | Process.Start(startInfo); 189 | } 190 | 191 | static private void getTables(Model model) { 192 | Console.WriteLine(consoleDelimeter); 193 | myConsoleWriteLine($" 1 Display to Screen"); 194 | myConsoleWriteLine($" 2 Open as TMSL"); 195 | Console.WriteLine(consoleDelimeter); 196 | String userInput = Console.ReadLine(); 197 | if (userInput == "2") { 198 | getTablesAsObjects(model); 199 | } 200 | else { 201 | foreach (Table table in model.Tables) { 202 | myConsoleWriteLine($" Table = {table.Name}"); 203 | } 204 | } 205 | } 206 | 207 | static private void setServer() { 208 | string serverName = ""; 209 | Console.Clear(); 210 | Console.WriteLine(consoleDelimeter); 211 | myConsoleWriteLine("Enter or Pick a Server"); 212 | serverName = getData("Servers.dat"); 213 | if (server.Connected) { 214 | server.Disconnect(); 215 | } 216 | try { 217 | server.Connect(serverName); 218 | if (!serverName.ToUpper().StartsWith("LOCALHOST")) { 219 | saveData(appFolder + "Servers.dat", serverName); 220 | } 221 | Console.Clear(); 222 | } 223 | catch (Exception ex) { 224 | Console.WriteLine($"{ex.InnerException.ToString()}"); 225 | } 226 | } 227 | 228 | static private Dictionary setDBIndex() { 229 | 230 | Dictionary databases = new Dictionary(); 231 | int dbIndex = 0; 232 | 233 | foreach (Database database in server.Databases) { 234 | string databaseName = database.Name; 235 | if (!databases.ContainsValue(databaseName)) { 236 | databases.Add(dbIndex, databaseName); 237 | dbIndex++; 238 | } 239 | } 240 | return databases; 241 | } 242 | 243 | static private Model setModel() { 244 | Model model = null; 245 | if (server.Databases.Count == 1) { 246 | model = server.Databases[0].Model; 247 | } 248 | else { 249 | Dictionary x = setDBIndex(); 250 | Console.Clear(); 251 | Console.WriteLine(consoleDelimeter); 252 | myConsoleWriteLine($"Please pick a database:"); 253 | Console.WriteLine(consoleDelimeter); 254 | foreach (KeyValuePair database in x) { 255 | myConsoleWriteLine($" {database.Key} - {database.Value}"); 256 | } 257 | Console.WriteLine(consoleDelimeter); 258 | String s = Console.ReadLine(); 259 | int dbIndex = int.Parse(s); 260 | 261 | if (x.ContainsKey(int.Parse(s))) { 262 | string db; 263 | x.TryGetValue(dbIndex, out db); 264 | model = server.Databases[db].Model; 265 | } 266 | Console.Clear(); 267 | } 268 | return model; 269 | } 270 | 271 | static private void showConsoleHeader(Model model) { 272 | Console.WriteLine(consoleDelimeter); 273 | myConsoleWriteLine($"Server : {server.ConnectionString}"); 274 | myConsoleWriteLine($"Database: {model.Database.Name}"); 275 | Console.WriteLine(consoleDelimeter); 276 | } 277 | 278 | static private void getTableProcessedState(Model model) { 279 | foreach (Table table in model.Tables) { 280 | foreach (Partition partition in table.Partitions) { 281 | String s = String.Format("{0,-30}{1,-30}{2,-20}", 282 | table.Name, 283 | partition.Name, 284 | partition.State.ToString() ); 285 | myConsoleWriteLine(s); 286 | } 287 | } 288 | } 289 | 290 | static private string getData(String filename) { 291 | filename = appFolder + filename; 292 | Console.WriteLine(consoleDelimeter); 293 | if (!System.IO.File.Exists(filename)) { 294 | System.IO.File.AppendAllText(filename, ""); 295 | } 296 | String[] lastData = System.IO.File.ReadAllLines(filename); 297 | if (filename.Contains("Server")) { 298 | List p = new List(); 299 | p.AddRange(lastData); 300 | p.AddRange(getProcesses()); 301 | String[] x = p.ToArray(); 302 | lastData = p.ToArray(); 303 | } 304 | 305 | int i = 0; 306 | foreach (string q in lastData) { 307 | myConsoleWriteLine($"{i} {q}"); 308 | i++; 309 | } 310 | 311 | Console.WriteLine(consoleDelimeter); 312 | 313 | String userInput = Console.ReadLine(); 314 | 315 | if (userInput.ToUpper().StartsWith("DEL")) { 316 | //TODO: delete code here 317 | ProcessStartInfo startInfo = new ProcessStartInfo(); 318 | startInfo.FileName = "NOTEPAD.EXE"; 319 | startInfo.Arguments = filename; 320 | Process.Start(startInfo); 321 | } 322 | else { 323 | String[] lines = { userInput }; 324 | if (Int32.TryParse(userInput, out int queryNumber)) { 325 | userInput = lastData[queryNumber]; 326 | } 327 | else { 328 | saveData(filename, userInput); 329 | } 330 | } 331 | return userInput; 332 | } 333 | 334 | public static string LookupProcess(int pid) { 335 | string procName; 336 | try { procName = Process.GetProcessById(pid).ProcessName; } 337 | catch (Exception) { procName = "-"; } 338 | return procName; 339 | } 340 | 341 | static private List getProcesses() { 342 | Process p = new Process(); 343 | ProcessStartInfo ps = new ProcessStartInfo(); 344 | ps.Arguments = "-ano"; 345 | ps.FileName = "netstat.exe"; 346 | ps.UseShellExecute = false; 347 | ps.WindowStyle = ProcessWindowStyle.Hidden; 348 | ps.RedirectStandardInput = true; 349 | ps.RedirectStandardOutput = true; 350 | ps.RedirectStandardError = true; 351 | p.StartInfo = ps; 352 | p.Start(); 353 | StreamReader stdOutput = p.StandardOutput; 354 | StreamReader stdError = p.StandardError; 355 | string content = stdOutput.ReadToEnd() + stdError.ReadToEnd(); 356 | string exitStatus = p.ExitCode.ToString(); 357 | // var Ports = new List(); 358 | List myPorts = new List { }; 359 | string[] rows = Regex.Split(content, "\r\n"); 360 | foreach (string row in rows) { 361 | //Split it baby 362 | string[] tokens = Regex.Split(row, "\\s+"); 363 | if (tokens.Length > 4 && (tokens[1].Equals("UDP") || tokens[1].Equals("TCP"))) { 364 | string localAddress = Regex.Replace(tokens[2], @"\[(.*?)\]", "1.1.1.1"); 365 | string pname = tokens[1] == "UDP" ? LookupProcess(Convert.ToInt32(tokens[4])) : LookupProcess(Convert.ToInt32(tokens[5])); 366 | string pnumber = "localhost:" + localAddress.Split(':')[1]; 367 | if (pname == "msmdsrv" && !myPorts.Contains(pnumber)) { 368 | myPorts.Add(pnumber); 369 | } 370 | } 371 | } 372 | return myPorts; 373 | } 374 | 375 | static private void saveData(String filename, String line) { 376 | String[] lines = System.IO.File.ReadAllLines(filename); 377 | 378 | bool lineFound = false; 379 | foreach (string l in lines) { 380 | if (l == line) { 381 | lineFound = true; 382 | } 383 | } 384 | 385 | string[] writeThis = { line }; 386 | if (!lineFound) { 387 | System.IO.File.AppendAllLines(filename, writeThis); 388 | } 389 | } 390 | 391 | 392 | static private void executeDAX(Model model) { 393 | Console.Clear(); 394 | showConsoleHeader(model); 395 | myConsoleWriteLine($"Enter or Pick a Query"); 396 | string query = getData("Query.dat"); 397 | AdomdConnection adomdConnection = new AdomdConnection($"Data Source={model.Database.Parent.ConnectionString};Initial catalog={model.Database.Name}"); 398 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection); 399 | adomdConnection.Open(); 400 | String queryResultFileName = $"{appFolder}QueryResult-{Guid.NewGuid()}.tsv"; 401 | List list = new List(); 402 | bool hasHeader = false; 403 | try { 404 | AdomdDataReader reader = adomdCommand.ExecuteReader(); 405 | while (reader.Read()) { 406 | String rowResults = ""; 407 | /***************************************************************************** 408 | Add Header (if needed) 409 | ****************************************************************************/ 410 | if (!hasHeader) { 411 | for (nt columnNumber = 0; columnNumber < reader.FieldCount; columnNumber++ ) { 412 | if (columnNumber > 0) { 413 | rowResults += $"\t"; 414 | } 415 | rowResults += $"{reader.GetName(columnNumber)}"; 416 | } 417 | Console.WriteLine(rowResults); 418 | list.Add(rowResults); 419 | hasHeader = true; 420 | } 421 | /***************************************************************************** 422 | Add normal line 423 | ****************************************************************************/ 424 | rowResults = ""; 425 | // Create a loop for every column in the current row 426 | for (int columnNumber = 0; columnNumber < reader.FieldCount; columnNumber++ ) { 427 | if (columnNumber > 0) { 428 | rowResults += $"\t"; 429 | } 430 | rowResults += $"{reader.GetValue(columnNumber)}"; 431 | } 432 | Console.WriteLine(rowResults); 433 | list.Add(rowResults); 434 | } 435 | 436 | System.IO.File.WriteAllLines(queryResultFileName, list); 437 | ProcessStartInfo startInfo = new ProcessStartInfo(); 438 | bool excelFound = false; 439 | if (File.Exists("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 440 | startInfo.FileName = "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 441 | excelFound = true; 442 | } 443 | else { 444 | if (File.Exists("C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) { 445 | startInfo.FileName = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE"; 446 | excelFound = true; 447 | } 448 | } 449 | 450 | if (excelFound) { 451 | startInfo.Arguments = queryResultFileName; 452 | Process.Start(startInfo); 453 | } 454 | 455 | } 456 | catch (Exception ex) { 457 | Console.WriteLine(ex.Message); 458 | Console.ReadKey(); 459 | } 460 | adomdConnection.Close(); 461 | } 462 | } 463 | --------------------------------------------------------------------------------