/// <summary> /// Add the whole set of items /// </summary> /// <param name="items"></param> public KeyedLookup(IEnumerable <T> items, TaskStatusLogs statusLogger) { foreach (var thisItem in items) { AddItem(thisItem.Id, thisItem, statusLogger); } }
/// <summary> /// Adds a keyed item to the dictionary /// </summary> /// <param name="key"></param> /// <param name="item"></param> /// <param name="statusLogger">If non-NULL; then trap and record errors, If NULL the error will get thrown upward</param> public void AddItem(string key, T item, TaskStatusLogs statusLogger = null) { //There are cases where building the dictionary may fail, such as if the incoming data has //duplicate ID entries. If we have a status logger, we want to log the error and then //continue onward try { _dictionary.Add(key, item); } catch (Exception exAddDictionaryItem) { //If we have an error logger, then log the error if (statusLogger != null) { string itemDescription = "null item"; if (item != null) { itemDescription = item.ToString(); } statusLogger.AddError("Error building lookup dictionary. Item: " + itemDescription + ", " + exAddDictionaryItem.ToString()); } else //Otherwise thrown the error upward { throw; } } }
/// <summary> /// Constructor /// </summary> /// <param name="pathTwbx">TWBX we are going to unpack</param> /// <param name="workingDirectory"></param> public TwbDataSourceEditor(string pathTwbInput, string pathTwbOutput, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { _pathToTwbInput = pathTwbInput; _pathToTwbOutput = pathTwbOutput; _serverMapInfo = serverMapInfo; _statusLog = statusLog; }
/// <summary> /// Constructor /// </summary> /// <param name="jobName">Name to associate with this work</param> /// <param name="onlineUrls"></param> /// <param name="userName"></param> /// <param name="password"></param> /// <param name="taskOptions"></param> /// <param name="manualActions"></param> public TaskMaster( string jobName, TableauServerUrls onlineUrls, string userName, string password, TaskMasterOptions taskOptions, CustomerManualActionManager manualActions = null) { this.JobName = jobName; _manualActions = manualActions; if (_manualActions == null) { _manualActions = new CustomerManualActionManager(); } //Get any export path _exportToLocalPath = taskOptions.GetOptionValue(TaskMasterOptions.OptionParameter_PathDownloadTo); _onlineUrls = onlineUrls; _userName = userName; _password = password; //Store the status log at the class level where it is accessable _statusLog = new TaskStatusLogs(); //Store the options _taskOptions = taskOptions; if (_taskOptions.IsOptionSet(TaskMasterOptions.Option_LogVerbose)) { _statusLog.SetStatusLoggingLevel(int.MinValue); } }
private void btnProvisionFromAzureAd_Click(object sender, EventArgs e) { var statusLogs = new TaskStatusLogs(); statusLogs.AddStatus("Starting..."); UpdateStatusText(statusLogs, true); string pathSecrets = txtPathToSecrets.Text; if (!File.Exists(pathSecrets)) { MessageBox.Show("Secrets file does not exist at specified path (" + pathSecrets + ")"); return; } string pathProvisionPlan = txtPathToAzureAdProvisioningConfig.Text; if (!File.Exists(pathProvisionPlan)) { MessageBox.Show("Config file does not exist at specified path (" + pathProvisionPlan + ")"); return; } string pathOutput = Path.Combine( Path.GetDirectoryName(pathProvisionPlan), "out"); FileIOHelper.CreatePathIfNeeded(pathOutput); //Show the user a command line that they can use to run this same work GenerateProvisioningCommandLine( CommandLineParser.Command_ProvisionFromAzure, pathSecrets, pathProvisionPlan, pathOutput); //Run the work try { ProvisionFromAzureAd( statusLogs, pathSecrets, txtPathToAzureAdProvisioningConfig.Text, pathOutput); } catch (Exception exError) { MessageBox.Show("Error: " + exError.Message); } UpdateStatusText(statusLogs, true); //Open the file explorer to the output directory if (Directory.Exists(pathOutput)) { System.Diagnostics.Process.Start(pathOutput); } }
/// <summary> /// Provision the site based on the provisioning manifest in a file /// </summary> /// <param name="statusLogs">Store status logs here</param> /// <param name="pathSecrets">Where the log in secrets are</param> /// <param name="pathProvisioningManifest">Where the provisioning steps are</param> /// <param name="outputPath">Where output files go</param> private void ProvisionFromFileManifest(TaskStatusLogs statusLogs, string pathSecrets, string pathProvisioningManifest, string outputPath) { //Load the config from the files var secretsConfig = new ProvisionConfigSiteAccess(pathSecrets); //Load the user provisioning instructions var provisionUsersInfo = new ProvisionUserInstructions( pathProvisioningManifest); var provisionSite = new ProvisionSite(secretsConfig, provisionUsersInfo, this, statusLogs); provisionSite.Execute(); //--------------------------------------------------------------------- //Generate an output file //--------------------------------------------------------------------- FileIOHelper.CreatePathIfNeeded(outputPath); var outputFilePath = Path.Combine(outputPath, "ProvisionSiteOutput.csv"); provisionSite.CSVResultsReport.GenerateCSVFile(outputFilePath); statusLogs.AddStatusHeader("Done!"); ((IShowLogs)this).NewLogResultsToShow(statusLogs); }
/// <summary> /// Shows status text in the textboxes /// </summary> /// <param name="statusLog"></param> private void UpdateStatusText(TaskStatusLogs statusLog) { textBoxStatus.Text = statusLog.StatusText; ScrollToEndOfTextbox(textBoxStatus); textBoxErrors.Text = statusLog.ErrorText; }
/// <summary> /// Load the DB credentials set from a file /// </summary> /// <param name="pathDBCredentials"></param> /// <returns></returns> internal static CredentialManager LoadFromFile(string pathDBCredentials, TaskStatusLogs statusLog) { if (statusLog == null) { statusLog = new TaskStatusLogs(); } statusLog.AddStatus("Loading database credentials from " + pathDBCredentials); //Load the XML document and get the credentials var xDoc = new XmlDocument(); xDoc.Load(pathDBCredentials); var nodesList = xDoc.SelectNodes("//credential"); var credentialManager = new CredentialManager(); foreach (XmlNode credentialNode in nodesList) { try { helper_parseCredentialNode(credentialManager, credentialNode); } catch (Exception ex) { statusLog.AddError("Error parsing credential, " + ex.Message + ", " + credentialNode.OuterXml); } } return(credentialManager); }
/// <summary> /// Constructor /// </summary> /// <param name="jobName">Name to associate with this work</param> /// <param name="onlineUrls"></param> /// <param name="userName"></param> /// <param name="password"></param> /// <param name="taskOptions"></param> /// <param name="manualActions"></param> public TaskMaster( string jobName, TableauServerUrls onlineUrls, string userName, string password, TaskMasterOptions taskOptions, CustomerManualActionManager manualActions = null) { this.JobName = jobName; _manualActions = manualActions; if(_manualActions == null) { _manualActions = new CustomerManualActionManager(); } //Get any export path _exportToLocalPath = taskOptions.GetOptionValue(TaskMasterOptions.OptionParameter_PathDownloadTo); _onlineUrls = onlineUrls; _userName = userName; _password = password; //Store the status log at the class level where it is accessable _statusLog = new TaskStatusLogs(); //Store the options _taskOptions = taskOptions; if(_taskOptions.IsOptionSet(TaskMasterOptions.Option_LogVerbose)) { _statusLog.SetStatusLoggingLevel(int.MinValue); } }
/// <summary> /// Constructor. Builds the data for the CSV file /// </summary> /// <param name="projects"></param> /// <param name="dataSources"></param> /// <param name="workbooks"></param> /// <param name="users"></param> /// <param name="groups"></param> public CustomerSiteInventory( IEnumerable<SiteProject> projects, IEnumerable<SiteDatasource> dataSources, IEnumerable<SiteWorkbook> workbooks, IEnumerable<SiteUser> users, IEnumerable<SiteGroup> groups, TaskStatusLogs statusLogger) { //Somewhere to store status logs if (statusLogger == null) { statusLogger = new TaskStatusLogs(); } this.StatusLog = statusLogger; //If we have a user-set, put it into a lookup class so we can quickly look up user names when we write out other data //that has user ids if(users != null) { _siteUserMapping = new KeyedLookup<SiteUser>(users); } AddProjectsData(projects); AddDatasourcesData(dataSources); AddWorkbooksData(workbooks); AddUsersData(users); AddGroupsData(groups); }
/// <summary> /// Load the DB credentials set from a file /// </summary> /// <param name="pathDBCredentials"></param> /// <returns></returns> internal static CredentialManager LoadFromFile(string pathDBCredentials, TaskStatusLogs statusLog) { if(statusLog == null) { statusLog = new TaskStatusLogs(); } statusLog.AddStatus("Loading database credentials from " + pathDBCredentials); //Load the XML document and get the credentials var xDoc = new XmlDocument(); xDoc.Load(pathDBCredentials); var nodesList = xDoc.SelectNodes("//credential"); var credentialManager = new CredentialManager(); foreach (XmlNode credentialNode in nodesList) { try { helper_parseCredentialNode(credentialManager, credentialNode); } catch(Exception ex) { statusLog.AddError("Error parsing credential, " + ex.Message + ", " + credentialNode.OuterXml); } } return credentialManager; }
/// <summary> /// Constructor. Builds the data for the CSV file /// </summary> /// <param name="projects"></param> /// <param name="dataSources"></param> /// <param name="workbooks"></param> /// <param name="users"></param> /// <param name="groups"></param> public CustomerSiteInventory( IEnumerable <SiteProject> projects, IEnumerable <SiteDatasource> dataSources, IEnumerable <SiteWorkbook> workbooks, IEnumerable <SiteUser> users, IEnumerable <SiteGroup> groups, TaskStatusLogs statusLogger) { //Somewhere to store status logs if (statusLogger == null) { statusLogger = new TaskStatusLogs(); } this.StatusLog = statusLogger; //If we have a user-set, put it into a lookup class so we can quickly look up user names when we write out other data //that has user ids if (users != null) { _siteUserMapping = new KeyedLookup <SiteUser>(users); } AddProjectsData(projects); AddDatasourcesData(dataSources); AddWorkbooksData(workbooks); AddUsersData(users); AddGroupsData(groups); }
/// <summary> /// If we have Project Mapping information, generate a project based path for the download /// </summary> /// <param name="basePath"></param> /// <param name="projectList"></param> /// <param name="projectId"></param> /// <returns></returns> public static string EnsureProjectBasedPath(string basePath, IProjectsList projectList, IHasProjectId project, TaskStatusLogs statusLog) { //If we have no project list to do lookups in then just return the base path if (projectList == null) return basePath; //Look up the project name var projWithId = projectList.FindProjectWithId(project.ProjectId); if(projWithId == null) { statusLog.AddError("Project not found with id " + project.ProjectId); return basePath; } //Turn the project name into a directory name var safeDirectoryName = GenerateWindowsSafeFilename(projWithId.Name); var pathWithProject = Path.Combine(basePath, safeDirectoryName); //If needed, create the directory if(!Directory.Exists(pathWithProject)) { Directory.CreateDirectory(pathWithProject); } return pathWithProject; }
//readonly CsvDataGenerator _csvProvisionResults = null; /*/// <summary> * /// CSV for generated report * /// </summary> * public CsvDataGenerator CSVResultsReport * { * get * { * return _csvProvisionResults; * } * } */ /// <summary> /// Constructor /// </summary> /// <param name="config"></param> /// <param name="configSyncGroups"></param> /// <param name="showLogsHere"></param> /// <param name="statusLogs"></param> /// <param name="ignoreAllUsersGroup">(True recommended) Do not export the 'all users' group</param> public TableauProvisionDownload( ProvisionConfigSiteAccess config, IShowLogs showLogsHere, TaskStatusLogs statusLogs, bool ignoreAllUsersGroup = true) { _ignoreAllUsersGroupInExport = ignoreAllUsersGroup; _showLogsHere = showLogsHere; _configTableauSecrets = config; if (statusLogs == null) { statusLogs = new TaskStatusLogs(); } _statusLogs = statusLogs; //Either use one passed in, or create one /*if (csvDataGenerator == null) * { * csvDataGenerator = new CsvDataGenerator(); * } */ //_csvProvisionResults = csvDataGenerator; }
/// <summary> /// Constructor /// </summary> /// <param name="pathTwbInput">TWB we are going to load and transform</param> /// <param name="pathTwbOutput">Output path for transformed CSV</param> /// <param name="oldDatasourceFilename">Old filename for the data source (case insensitive)</param> /// <param name="newCsvPath">Path to CSV file that we want the data source to point to</param> /// <param name="statusLog">Log status and errors here</param> public TwbReplaceCSVReference(string pathTwbInput, string pathTwbOutput, string oldDatasourceFilename, string newCsvPath, TaskStatusLogs statusLog) { _pathToTwbInput = pathTwbInput; _pathToTwbOutput = pathTwbOutput; _oldDatasourceFilename = oldDatasourceFilename; _datasourceNewCsvPath = newCsvPath; _statusLog = statusLog; }
/// <summary> /// Constructor /// </summary> /// <param name="pathTwbInput">TWB we are going to load and transform</param> /// <param name="pathTwbOutput">Output path for transformed CSV</param> /// <param name="dataSourceName">Name of data source inside path</param> /// <param name="newCsvPath">Path to CSV file that we want the data source to point to</param> /// <param name="statusLog">Log status and errors here</param> public TwbReplaceCSVReference(string pathTwbInput, string pathTwbOutput, string dataSourceName, string newCsvPath, TaskStatusLogs statusLog) { _pathToTwbInput = pathTwbInput; _pathToTwbOutput = pathTwbOutput; _datasourceName = dataSourceName; _datasourceNewCsvPath = newCsvPath; _statusLog = statusLog; }
/// <summary> /// Constructor /// </summary> /// <param name="onlineUrls"></param> /// <param name="userName"></param> /// <param name="password"></param> /// <param name="statusLog"></param> public TableauServerSignIn(TableauServerUrls onlineUrls, string userName, string password, TaskStatusLogs statusLog) { if (statusLog == null) { statusLog = new TaskStatusLogs(); } this.StatusLog = statusLog; _onlineUrls = onlineUrls; _userName = userName; _password = password; SiteUrlSegment = onlineUrls.SiteUrlSegement; }
/// <summary> /// Synchronous call to test and make sure sign in works /// </summary> /// <param name="url"></param> /// <param name="userId"></param> /// <param name="userPassword"></param> /// <param name="statusLog"></param> public static void VerifySignInPossible(string url, string userId, string userPassword, TaskStatusLogs statusLog) { var urlManager = TableauServerUrls.FromContentUrl(url, TaskMasterOptions.RestApiReponsePageSizeDefault); var signIn = new TableauServerSignIn(urlManager, userId, userPassword, statusLog); bool success = signIn.ExecuteRequest(); if(!success) { throw new Exception("Failed sign in"); } }
/// <summary> /// Constructor /// </summary> /// <param name="config"></param> /// <param name="provisionInstructions"></param> /// <param name="showLogsHere"></param> /// <param name="statusLogs"></param> public ProvisionSite(ProvisionConfigSiteAccess config, ProvisionUserInstructions provisionInstructions, IShowLogs showLogsHere, TaskStatusLogs statusLogs) { _showLogsHere = showLogsHere; _config = config; _provisionInstructions = provisionInstructions; if (statusLogs == null) { statusLogs = new TaskStatusLogs(); } _statusLogs = statusLogs; }
/// <summary> /// Constructor /// </summary> /// <param name="pathTwbx">TWBX we are going to unpack</param> /// <param name="workingDirectory"></param> public TwbxDataSourceEditor(string pathTwbx, string workingDirectory, ITableauServerSiteInfo serverInfo, TaskStatusLogs statusLog) { _pathToTwbx = pathTwbx; _pathToWorkIn = workingDirectory; _statusLog = statusLog; _serverInfo = serverInfo; if (!File.Exists(_pathToTwbx)) { throw new ArgumentException("Original file does not exist " + _pathToTwbx); } }
/// <summary> /// Shows status text in the textboxes /// </summary> /// <param name="statusLog"></param> private void UpdateStatusText(TaskStatusLogs statusLog, bool forceUIRefresh = false) { textBoxStatus.Text = statusLog.StatusText; ScrollToEndOfTextbox(textBoxStatus); textBoxErrors.Text = statusLog.ErrorText; if (forceUIRefresh) { textBoxStatus.Refresh(); textBoxErrors.Refresh(); } }
/// <summary> /// Create a sign in manager for the given user /// </summary> /// <param name="url">Tableau site url</param> /// <param name="username">Tableau username</param> /// <param name="password">Tableau user's password</param> /// <param name="statusLog">Status log</param> public TableauServerSignIn(TableauServerUrls url, string username, string password, TaskStatusLogs statusLog) { if (statusLog == null) { statusLog = new TaskStatusLogs(); } StatusLog = statusLog; _onlineUrls = url; _userName = username; _password = password; _siteUrlSegment = url.SiteUrlSegement; }
/// <summary> /// Sanity cehck for sign in /// </summary> /// <param name="siteUrl"></param> /// <param name="signInUser"></param> /// <param name="signInPassword"></param> /// <returns></returns> private bool ValidateSignInPossible(string siteUrl, bool useAccessToken, string signInUser, string signInPassword) { var testSignInStatusLog = new TaskStatusLogs(); testSignInStatusLog.SetStatusLoggingLevel(int.MinValue); try { TableauServerSignIn.VerifySignInPossible(siteUrl, useAccessToken, signInUser, signInPassword, testSignInStatusLog); } catch { MessageBox.Show("Sign in to your Tableau Server failed. Please check URL and credentials"); textBoxStatus.Text = testSignInStatusLog.StatusText; textBoxErrors.Text = testSignInStatusLog.ErrorText; return(false); } return(true); }
/// <summary> /// Called to run us in commandn line mode /// </summary> /// <param name="commandLine"></param> internal void RunStartupCommandLine_Inner(TaskStatusLogs statusLogs) { statusLogs.AddStatusHeader("Processing command line"); string pathProvisionPlan = AppSettings.CommandLine_PathProvisionPlan; string pathSecrets = AppSettings.CommandLine_PathSecrets; string pathOutput = AppSettings.CommandLine_PathOutput; //If an output directory was not specified, then output into an "out" subdirectory in the directory where the provision plan is if (string.IsNullOrWhiteSpace(pathOutput)) { pathOutput = Path.Combine( Path.GetDirectoryName(pathProvisionPlan), "out"); } //==================================================================================== //Based on the command specified, run the specified task //==================================================================================== switch (AppSettings.CommandLine_Command) { case CommandLineParser.Command_ProvisionFromAzure: //Update the paths in the UI so the user can see & re-run them if they want txtPathToSecrets.Text = pathSecrets; txtPathToAzureAdProvisioningConfig.Text = pathProvisionPlan; //Run the work... ProvisionFromAzureAd(statusLogs, pathSecrets, pathProvisionPlan, pathOutput); break; case CommandLineParser.Command_ProvisionFromFileManifest: //Update the paths in the UI so the user can see & re-run them if they want txtPathToSecrets.Text = pathSecrets; txtPathToFileProvisioningConfig.Text = pathProvisionPlan; //Run the work... ProvisionFromFileManifest(statusLogs, pathSecrets, pathProvisionPlan, pathOutput); break; default: statusLogs.AddError("1101-432: Unknown command: " + AppSettings.CommandLine_Command); break; } }
/// <summary> /// Constructor /// </summary> /// <param name="onlineUrls"></param> /// <param name="signInClientId">Email or Token name</param> /// <param name="signInSecret">Password or Secret Token</param> /// <param name="statusLog"></param> public TableauServerSignIn( TableauServerUrls onlineUrls, string signInClientId, string signInSecret, TaskStatusLogs statusLog, SignInMode signInMode = SignInMode.UserNameAndPassword) { if (statusLog == null) { statusLog = new TaskStatusLogs(); } this.StatusLog = statusLog; _onlineUrls = onlineUrls; _signInClientId = signInClientId; _signInSecret = signInSecret; _signInMode = signInMode; SiteUrlSegment = onlineUrls.SiteUrlSegement; }
/// <summary> /// Finds and changes a datasource reference inside a Workbook. Changes the CSV file the data source points to /// </summary> /// <param name="xmlDoc"></param> /// <param name="datasourceName"></param> /// <param name="pathToTargetCsv"></param> /// <param name="statusLog"></param> private bool RemapDatasourceCsvReference(XmlDocument xmlDoc, string datasourceName, string pathToTargetCsv, TaskStatusLogs statusLog) { int replaceItemCount = 0; string newCsvDirectory = Path.GetDirectoryName(_datasourceNewCsvPath); string newCsvFileName = Path.GetFileName(_datasourceNewCsvPath); string newDatasourceRelationName = Path.GetFileNameWithoutExtension(newCsvFileName) + "#csv"; string newDatasourceRelationTable = "[" + newDatasourceRelationName + "]"; string seekDatasourceCaption = _datasourceName; var xDataSources = xmlDoc.SelectNodes("workbook/datasources/datasource"); if(xDataSources != null) { //Look through the data sources foreach (XmlNode xnodeDatasource in xDataSources) { //If the data source is matching the caption we are looking for if(XmlHelper.SafeParseXmlAttribute(xnodeDatasource, "caption", "") == seekDatasourceCaption) { var xnodeConnection = xnodeDatasource.SelectSingleNode("connection"); //It should be 'textscan', it would be unexpected if it were not if(XmlHelper.SafeParseXmlAttribute(xnodeConnection, "class", "") == "textscan") { //Point to the new directory/path xnodeConnection.Attributes["directory"].Value = newCsvDirectory; xnodeConnection.Attributes["filename"].Value = newCsvFileName; //And it's got a Relation we need to update var xNodeRelation = xnodeConnection.SelectSingleNode("relation"); xNodeRelation.Attributes["name"].Value = newDatasourceRelationName; xNodeRelation.Attributes["table"].Value = newDatasourceRelationTable; replaceItemCount++; } else { _statusLog.AddError("Data source remap error. Expected data source to be 'textscan'"); } }//end if }//end foreach }//end if return replaceItemCount > 0; }
/// <summary> /// Remaps global references in the workbook that refer to the site/server /// </summary> /// <param name="xmlDoc"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapWorkbookGlobalReferences(XmlDocument xmlDoc, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { var xnodeWorkbook = xmlDoc.SelectSingleNode("//workbook"); if(xnodeWorkbook == null) { statusLog.AddError("Workbook remapper, 'workbook' node not found"); return; } //See if there is an an XML base node var attrXmlBase = xnodeWorkbook.Attributes["xml:base"]; if(attrXmlBase != null) { attrXmlBase.Value = serverMapInfo.ServerNameWithProtocol; } //We may also have a repository node RemapSingleWorkbooksRepositoryNode(xmlDoc, xnodeWorkbook, serverMapInfo, true, statusLog); }
//Update to: -<connection username="" server="preview-online.tableau.com" port="443" directory="/dataserver" dbname="At-Task60days" class="sqlproxy" channel="https"> private static void RemapDataServerReferences(XmlDocument xmlDoc, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { var xDataSources = xmlDoc.SelectNodes("workbook/datasources/datasource"); foreach (XmlNode xnodeDatasource in xDataSources) { var xnodeConnection = xnodeDatasource.SelectSingleNode("connection"); //Not all datasources have connection nodes (e.g. the parameters data source). If there is no connection, there is nothing to do if(xnodeConnection != null) { string dbClass = xnodeConnection.Attributes["class"].Value; //If its 'sqlproxy' then its a data server connection if (dbClass == "sqlproxy") { //Start remapping.... RemapSingleDataServerConnectionNode(xnodeDatasource, serverMapInfo, statusLog); RemapSingleDataServerRepositoryNode(xmlDoc, xnodeDatasource, serverMapInfo, false, statusLog); } } } }
/// <summary> /// Called to run a command line task /// </summary> internal void RunStartupCommandLine() { var statusLogs = new TaskStatusLogs(); try { RunStartupCommandLine_Inner(statusLogs); } catch (Exception ex) { IwsDiagnostics.Assert(false, "1101-445: Command line error: " + ex.Message); } //If we are supposed to exit, then do so... if (AppSettings.CommandLine_ExitWhenDone) { ExitApplication(); return; } UpdateStatusText(statusLogs); }
/// <summary> /// Constructor /// </summary> /// <param name="config"></param> /// <param name="configSyncGroups"></param> /// <param name="showLogsHere"></param> /// <param name="statusLogs"></param> /// <param name="csvDataGenerator"></param> public AzureDownload( AzureAdConfig config, ProvisionConfigExternalDirectorySync configSyncGroups, IShowLogs showLogsHere, TaskStatusLogs statusLogs, CsvDataGenerator csvDataGenerator) { _showLogsHere = showLogsHere; _configAzure = config; _configSyncGroups = configSyncGroups; if (statusLogs == null) { statusLogs = new TaskStatusLogs(); } _statusLogs = statusLogs; //Either use one passed in, or create one if (csvDataGenerator == null) { csvDataGenerator = new CsvDataGenerator(); } _csvProvisionResults = csvDataGenerator; }
/// <summary> /// Remaps necesary attributes inside of the datasource->connection node to point to a new server /// </summary> /// <param name="xDSourceConnection"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleDataServerConnectionNode(XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { //Get the XML sub mode we need var xNodeConnection = xNodeDatasource.SelectSingleNode("connection"); if(xNodeConnection == null) { statusLog.AddError("Workbook remapper, no 'connection' node found"); return; } //==================================================================================== //PORT NUMBER //==================================================================================== var attrPort = xNodeConnection.Attributes["port"]; if(attrPort != null) { if(serverMapInfo.Protocol == ServerProtocol.http) { attrPort.Value = "80"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrPort.Value = "443"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'port'"); } //==================================================================================== //Server name //==================================================================================== var attrServer = xNodeConnection.Attributes["server"]; if (attrServer != null) { attrServer.Value = serverMapInfo.ServerName; } else { statusLog.AddError("Workbook remapper, missing attribute 'server'"); } //==================================================================================== //Channel //==================================================================================== var attrChannel = xNodeConnection.Attributes["channel"]; if (attrChannel != null) { if (serverMapInfo.Protocol == ServerProtocol.http) { attrChannel.Value = "http"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrChannel.Value = "https"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'channel'"); } }
/// <summary> /// Sanity cehck for sign in /// </summary> /// <param name="siteUrl"></param> /// <param name="signInUser"></param> /// <param name="signInPassword"></param> /// <returns></returns> private bool ValidateSignInPossible(string siteUrl, string signInUser, string signInPassword) { var testSignInStatusLog = new TaskStatusLogs(); testSignInStatusLog.SetStatusLoggingLevel(int.MinValue); try { TableauServerSignIn.VerifySignInPossible(siteUrl, signInUser, signInPassword, testSignInStatusLog); } catch { MessageBox.Show("Sign in to your Tableau Server failed. Please check URL and credentials"); textBoxStatus.Text = testSignInStatusLog.StatusText; textBoxErrors.Text = testSignInStatusLog.ErrorText; return false; } return true; }
/// <summary> /// Attempt to log any detailed information we find about the failed web request /// </summary> /// <param name="webException"></param> /// <param name="onlineStatusLog"></param> private static void AttemptToLogWebException(WebException webException, string description, TaskStatusLogs onlineStatusLog) { if (onlineStatusLog == null) { return; //No logger? nothing to do } try { if (string.IsNullOrWhiteSpace(description)) { description = "web request failed"; } var response = webException.Response; var responseText = GetWebResponseAsText(response); response.Close(); if (responseText == null) { responseText = ""; } onlineStatusLog.AddError(description + ": " + webException.Message + "\r\n" + responseText + "\r\n"); } catch (Exception ex) { onlineStatusLog.AddError("Error in web request exception: " + ex.Message); return; } }
/// <summary> /// Attempt to log any detailed information we find about the failed web request /// </summary> /// <param name="webException"></param> /// <param name="onlineStatusLog"></param> private static void AttemptToLogWebException(WebException webException, string description, TaskStatusLogs onlineStatusLog) { if (onlineStatusLog == null) { return; //No logger? nothing to do } try { if (string.IsNullOrWhiteSpace(description)) { description = "web request failed"; } string responseText = ""; //NOTE: In some cases (e.g. time-out) the response may be NULL var response = webException.Response; if (response != null) { responseText = GetWebResponseAsText(response); response.Close(); } //Cannonicalize a blank result... if (string.IsNullOrEmpty(responseText)) { responseText = ""; } onlineStatusLog.AddError(description + ": " + webException.Message + "\r\n" + responseText + "\r\n"); } catch (Exception ex) { onlineStatusLog.AddError("811-830: Error in web request exception: " + ex.Message); return; } }
/// <summary> /// Generate a maniefest file based on the current Online site /// </summary> /// <param name="statusLogs"></param> /// <param name="pathSecrets"></param> /// <param name="pathOutputFile"></param> /// <param name="ignoreAllUsersGroup">(recommend TRUE) If false, the manifest file will contain the "all users" group</param> private void GenerateManifestFromOnlineSite( TaskStatusLogs statusLogs, string pathSecrets, string pathOutputFile, bool ignoreAllUsersGroup) { var pathOutputs = Path.GetDirectoryName(pathOutputFile); ProvisionConfigSiteAccess secretsConfig; //=========================================================================================== //Get the sign in information //=========================================================================================== try { //Load the config from the files secretsConfig = new ProvisionConfigSiteAccess(pathSecrets); } catch (Exception exSignInConfig) { statusLogs.AddError("Error loading sign in config file"); throw new Exception("1012-327: Error parsing sign in config, " + exSignInConfig.Message); } //=========================================================================================== //Create a place for out output files //=========================================================================================== FileIOHelper.CreatePathIfNeeded(pathOutputs); var provisionSettings = ProvisionConfigExternalDirectorySync.FromDefaults(); //=========================================================================================== //Download all the data we need from the Tableau Online site //=========================================================================================== statusLogs.AddStatusHeader("Retrieving information from Tableau"); UpdateStatusText(statusLogs); var tableauDownload = new TableauProvisionDownload( secretsConfig, this, statusLogs, ignoreAllUsersGroup); try { tableauDownload.Execute(); } catch (Exception exTableauDownload) { statusLogs.AddError("Error retrieving data from Tableau"); throw new Exception("813-0148: Error in Tableau Download, " + exTableauDownload.Message); } //=========================================================================================== //Write the provisioning manifest out to a file //=========================================================================================== statusLogs.AddStatusHeader("Writing out manifest file for Tableau provisioning"); UpdateStatusText(statusLogs); var outputProvisioningRoles = tableauDownload.ProvisioningManifestResults; try { outputProvisioningRoles.GenerateProvisioningManifestFile(pathOutputFile, provisionSettings); } catch (Exception exWriteProvisioningManifest) { statusLogs.AddError("Error creating provisioning manifest"); throw new Exception("1012-252: Error writing provisioning manifest, " + exWriteProvisioningManifest.Message); } }
/// <summary> /// Attempt to log any detailed information we find about the failed web request /// </summary> /// <param name="webException"></param> /// <param name="onlineStatusLog"></param> private static void AttemptToLogWebException(WebException webException, string description, TaskStatusLogs onlineStatusLog) { if(onlineStatusLog == null) return; //No logger? nothing to do try { if(string.IsNullOrWhiteSpace(description)) { description = "web request failed"; } var response = webException.Response; var responseText = GetWebResponseAsText(response); response.Close(); if(responseText == null) responseText = ""; onlineStatusLog.AddError(description + ": " + webException.Message + "\r\n" + responseText + "\r\n"); } catch (Exception ex) { onlineStatusLog.AddError("Error in web request exception: " + ex.Message); return; } }
/// <summary> /// Called to generate a provisioning manifest file from a Tableau site /// This is useful for creating a "backup" of the sites existing users/groups /// provisioning /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnCreateBackupManifestFile_Click(object sender, EventArgs e) { var statusLogs = new TaskStatusLogs(); statusLogs.AddStatus("Starting..."); UpdateStatusText(statusLogs, true); bool ignoreAllUsersGroup = chkIgoreAllUsersGroup.Checked; string pathSecrets = txtPathToSecrets.Text; if (!File.Exists(pathSecrets)) { MessageBox.Show("Secrets file does not exist at specified path (" + pathSecrets + ")"); return; } string pathOutputManifestFile = txtPathToGenerateManifestFile.Text; //If they gave us a directory, not a file-name, then add filename to it if (Directory.Exists(pathOutputManifestFile)) { pathOutputManifestFile = Path.Combine( pathOutputManifestFile, FileIOHelper.FilenameWithDateTimeUnique("SiteProvisionManifest.xml")); //Show it in the UI txtPathToGenerateManifestFile.Text = pathOutputManifestFile; } var pathOutput = Path.GetDirectoryName(pathOutputManifestFile); FileIOHelper.CreatePathIfNeeded(pathOutput); //Show the user a command line that they can use to run this same work GenerateProvisioningCommandLine( CommandLineParser.Command_GenerateManifestFromOnlineSite, pathSecrets, pathOutputManifestFile, pathOutput, ignoreAllUsersGroup); //Run the work try { GenerateManifestFromOnlineSite( statusLogs, pathSecrets, pathOutputManifestFile, ignoreAllUsersGroup); } catch (Exception exError) { MessageBox.Show("Error: " + exError.Message); } UpdateStatusText(statusLogs, true); //Open the file explorer to the output directory if (Directory.Exists(pathOutput)) { System.Diagnostics.Process.Start(pathOutput); } }
//Update to: -<connection username="" server="preview-online.tableau.com" port="443" directory="/dataserver" dbname="At-Task60days" class="sqlproxy" channel="https"> private static void RemapDataServerReferences(XmlDocument xmlDoc, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { var xDataSources = xmlDoc.SelectNodes("workbook/datasources/datasource"); foreach (XmlNode xnodeDatasource in xDataSources) { var xnodeConnection = xnodeDatasource.SelectSingleNode("connection"); //Not all datasources have connection nodes (e.g. the parameters data source). If there is no connection, there is nothing to do if (xnodeConnection != null) { string dbClass = xnodeConnection.Attributes["class"].Value; //If its 'sqlproxy' then its a data server connection if (dbClass == "sqlproxy") { //Start remapping.... RemapSingleDataServerConnectionNode(xnodeDatasource, serverMapInfo, statusLog); RemapSingleDataServerRepositoryNode(xmlDoc, xnodeDatasource, serverMapInfo, false, statusLog); } } } }
/// <summary> /// Remaps global references in the workbook that refer to the site/server /// </summary> /// <param name="xmlDoc"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapWorkbookGlobalReferences(XmlDocument xmlDoc, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { var xnodeWorkbook = xmlDoc.SelectSingleNode("//workbook"); if (xnodeWorkbook == null) { statusLog.AddError("Workbook remapper, 'workbook' node not found"); return; } //See if there is an an XML base node var attrXmlBase = xnodeWorkbook.Attributes["xml:base"]; if (attrXmlBase != null) { attrXmlBase.Value = serverMapInfo.ServerNameWithProtocol; } //We may also have a repository node RemapSingleWorkbooksRepositoryNode(xmlDoc, xnodeWorkbook, serverMapInfo, true, statusLog); }
/// <summary> /// Remaps necesary attributes inside of the datasource->connection node to point to a new server /// </summary> /// <param name="xDSourceConnection"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleDataServerConnectionNode(XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { //Get the XML sub mode we need var xNodeConnection = xNodeDatasource.SelectSingleNode("connection"); if (xNodeConnection == null) { statusLog.AddError("Workbook remapper, no 'connection' node found"); return; } //==================================================================================== //PORT NUMBER //==================================================================================== var attrPort = xNodeConnection.Attributes["port"]; if (attrPort != null) { if (serverMapInfo.Protocol == ServerProtocol.http) { attrPort.Value = "80"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrPort.Value = "443"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'port'"); } //==================================================================================== //Server name //==================================================================================== var attrServer = xNodeConnection.Attributes["server"]; if (attrServer != null) { attrServer.Value = serverMapInfo.ServerName; } else { statusLog.AddError("Workbook remapper, missing attribute 'server'"); } //==================================================================================== //Channel //==================================================================================== var attrChannel = xNodeConnection.Attributes["channel"]; if (attrChannel != null) { if (serverMapInfo.Protocol == ServerProtocol.http) { attrChannel.Value = "http"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrChannel.Value = "https"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'channel'"); } }
/// <summary> /// Synchronous call to test and make sure sign in works /// </summary> /// <param name="url"></param> /// <param name="userId"></param> /// <param name="userPassword"></param> /// <param name="statusLog"></param> public static void VerifySignInPossible(string url, string userId, string userPassword, TaskStatusLogs statusLog) { var urlManager = TableauServerUrls.FromContentUrl(url, TaskMasterOptions.RestApiReponsePageSizeDefault); var signIn = new TableauServerSignIn(urlManager, userId, userPassword, statusLog); bool success = signIn.Execute(); if (!success) { throw new Exception("Failed sign in"); } }
/// <summary> /// Remaps the 'repository-location' node of the Data Source XML /// </summary> /// <param name="xnodeRepository"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleWorkbooksRepositoryNode(XmlDocument xmlDoc, XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, bool ignoreIfMissing, TaskStatusLogs statusLog) { var siteId = serverMapInfo.SiteId; //Get the XML sub mode we need var xnodeRepository = xNodeDatasource.SelectSingleNode("repository-location"); if (xnodeRepository == null) { if (ignoreIfMissing) return; statusLog.AddError("Workbook remapper, no workbook 'repository-location' node found"); return; } /////////////////////////////////////////////////////////////////////////////////////// helper_SetRespositorySite(xmlDoc, xnodeRepository, serverMapInfo); /////////////////////////////////////////////////////////////////////////////////////// var attrPath = xnodeRepository.Attributes["path"]; if (attrPath != null) { //Is there a site specified if (!string.IsNullOrWhiteSpace(siteId)) { attrPath.Value = "/t/" + siteId + "/workbooks"; } else //Default site { attrPath.Value = "/workbooks"; } } else { statusLog.AddError("Workbook remapper 'path' attribute not found"); } }
/// <summary> /// Remaps the 'repository-location' node of the Data Source XML /// </summary> /// <param name="xnodeRepository"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleWorkbooksRepositoryNode(XmlDocument xmlDoc, XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, bool ignoreIfMissing, TaskStatusLogs statusLog) { var siteId = serverMapInfo.SiteId; //Get the XML sub mode we need var xnodeRepository = xNodeDatasource.SelectSingleNode("repository-location"); if (xnodeRepository == null) { if (ignoreIfMissing) { return; } statusLog.AddError("Workbook remapper, no workbook 'repository-location' node found"); return; } /////////////////////////////////////////////////////////////////////////////////////// helper_SetRespositorySite(xmlDoc, xnodeRepository, serverMapInfo); /////////////////////////////////////////////////////////////////////////////////////// var attrPath = xnodeRepository.Attributes["path"]; if (attrPath != null) { //Is there a site specified if (!string.IsNullOrWhiteSpace(siteId)) { attrPath.Value = "/t/" + siteId + "/workbooks"; } else //Default site { attrPath.Value = "/workbooks"; } } else { statusLog.AddError("Workbook remapper 'path' attribute not found"); } }